Move to new settings system

This commit is contained in:
Noah 2014-12-04 15:06:44 -08:00
parent 023d5f91df
commit ff75921129
20 changed files with 348 additions and 111 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
# Don't check in site specific settings.
config.py
settings.ini
# Compiled Python
*.pyc

180
defaults.ini Normal file
View File

@ -0,0 +1,180 @@
# Default configuration settings for Rophako - DO NOT EDIT THIS FILE!
#
# To configure your site, create a new file named "settings.yml" and override
# settings defined in this file. Your settings.yml is masked on top of the
# settings in defaults.yml.
#
# String values can substitute the following special variables:
# %(_basedir): The absolute path to the root of this git repository, such that
# ./rophako/app.py exists.
# %(_year): inserts the current year (for the RSS feed copyright setting)
# Constants that may be useful in this file.
[DEFAULT]
_admin_email = root@localhost
_date_format = %A, %B %d %Y @ %I:%M:%S %p
# "Weekday, Month dd yyyy @ hh:mm:ss AM"
#------------------------------------------------------------------------------#
# General Website Settings #
#------------------------------------------------------------------------------#
[site]
# Debug mode for development only!
debug = false
# Unique name of your site, e.g. "kirsle.net"
site_name = example.com
# Path to your site's HTML root. Whenever Rophako tries to render a
# template, it will check in your site's root for the template first before
# defaulting to the default fallback pages in the rophako/www folder. All
# of the core Rophako pages, e.g. for account, blog, photo albums and so on,
# have templates in the default site. You can override those templates by
# creating files with the same paths in your site's HTML folder.
site_root = %(_basedir)s/site/www
# E-mail address for site notifications (e.g. new comments and exceptions)
notify_address = %(_admin_email)s
# Where to save temp files for photo uploads etc.
tempdir = /tmp
#------------------------------------------------------------------------------#
# Database settings #
#------------------------------------------------------------------------------#
[db]
# Rophako uses a flat file JSON database system, and a Redis server sits
# between Rophako and the filesystem. The db_root is the path on the
# filesystem to store documents in (can be relative, default "./db")
db_root = db
redis_host = localhost
redis_port = 6379
redis_db = 0
redis_prefix = rophako:
#------------------------------------------------------------------------------#
# Security Settings #
#------------------------------------------------------------------------------#
[security]
# Set this value to true to force SSL/TLS use on your web app. Turning
# this on will do the following:
# - Send HTTP Strict-Transport-Security header
# - Use secure session cookies
force_ssl = false
# Secret key used for session cookie signing. Make this long and hard to
# guess.
#
# Tips for creating a strong secret key:
# $ python
# >>> import os
# >>> os.urandom(24)
# '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
#
# Then take that whole quoted string and paste it right in as the secret
# key! Do NOT use that one. It was just an example! Make your own.
secret_key = for the love of Arceus, change this key!
# Password strength: number of iterations for bcrypt password.
bcrypt_iterations = 12
#------------------------------------------------------------------------------#
# Mail Settings #
#------------------------------------------------------------------------------#
[mail]
# method = smtp or sendmail (not yet implemented)
method = smtp
server = localhost
port = 25
sender = Rophako CMS <no-reply@rophako.kirsle.net>
#------------------------------------------------------------------------------#
# Plugin Configurations #
#------------------------------------------------------------------------------#
###
# Emoticons
###
# Emoticon theme used for blog posts and comments. Should exist at the URL
# "/static/smileys" from your document root, and have a file named
# "emoticons.json" inside. If you add a custom theme to your private site
# folder, then also change EMOTICON_ROOT_PRIVATE to look there instead.
[emoticons]
theme = tango
root_private = %(_basedir)s/rophako/www/static/smileys
###
# Blog
###
[blog]
default_category = Uncategorized
default_privacy = public
time_format = %(_date_format)s
allow_comments = true
entries_per_page = 5
# RSS feed settings.
title = Rophako CMS Blog
link = http://rophako.kirsle.net/
language = en
description = The web blog of the Rophako CMS.
copyright = Copyright %(_year)s
webmaster = %(_admin_email)s
image_title = Rophako CMS Blog
image_url = //www.kirsle.net/static/avatars/default.png
image_width = 100
image_height = 100
image_description = Rophako CMS
entries_per_feed = 5
###
# Photo
###
[photo]
# The path to where uploaded photos will be stored.
# The PRIVATE path is from the perspective of the server file system.
# The PUBLIC path is from the perspective of the web browser via HTTP.
root_private = %(_basedir)s/site/www/static/photos
root_public = /static/photos
default_album = My Photos
time_format = %(_date_format)s
# Max widths for photo sizes
width_large = 800
width_thumb = 256
width_avatar = 96
###
# Comment
###
[comment]
time_format = %(_date_format)s
# 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.
default_avatar =
#------------------------------------------------------------------------------#
# List of Enabled Plugins #
#------------------------------------------------------------------------------#
[plugins]
# Which plugins to enable? List each plugin by module name. The plugins
# will be assumed to be blueprints that can be attached to the main app
# object. If you instead want to load an arbitrary Python module (i.e. to
# define custom routes at the app layer, not in a blueprint) list those
# under the "custom" section (remove the empty array [] and list them
# like shown in the plugins section).
blueprints =
rophako.modules.blog
rophako.modules.photo
rophako.modules.comment
rophako.modules.emoticons
rophako.modules.contact
rophako.modules.tracking
custom =

View File

@ -7,7 +7,7 @@ import re
import os
import json
import config
from rophako.settings import Config
from rophako.app import app
from rophako.utils import template, login_required
import rophako.model.blog as Blog
@ -109,6 +109,6 @@ def ssl_test():
},
"App Configuration": {
"Session cookies secure": app.config["SESSION_COOKIE_SECURE"],
"config.FORCE_SSL": config.FORCE_SSL,
"config.FORCE_SSL": Config.security.force_ssl,
},
}))

View File

@ -5,4 +5,4 @@ bcrypt
pillow
requests
Markdown
Pygments
Pygments

View File

@ -23,17 +23,20 @@ app = Flask(__name__,
# jinja2.ChoiceLoader.
BLUEPRINT_PATHS = []
import config
from rophako.settings import Config
Config.load_settings()
Config.load_plugins()
from rophako import __version__
from rophako.plugin import load_plugin
import rophako.model.tracking as Tracking
import rophako.utils
app.DEBUG = config.DEBUG
app.secret_key = config.SECRET_KEY
app.DEBUG = Config.site.debug == "true"
app.secret_key = Config.security.secret_key.decode("string_escape")
# Security?
if config.FORCE_SSL:
if Config.security.force_ssl == "true":
app.config['SESSION_COOKIE_SECURE'] = True
sslify = SSLify(app)
@ -44,8 +47,8 @@ load_plugin("rophako.modules.account")
# Custom Jinja handler to support custom- and default-template folders for
# rendering templates.
template_paths = [
config.SITE_ROOT, # Site specific.
"rophako/www", # Default/fall-back
Config.site.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])
@ -70,7 +73,7 @@ def before_request():
"version": __version__,
"python_version": "{}.{}".format(sys.version_info.major, sys.version_info.minor),
"author": "Noah Petherbridge",
"photo_url": config.PHOTO_ROOT_PUBLIC,
"photo_url": Config.photo.root_public,
},
"uri": request.path,
"session": {
@ -125,7 +128,7 @@ def catchall(path):
otherwise we give the 404 error page."""
# Search for this file.
for root in [config.SITE_ROOT, "rophako/www"]:
for root in [Config.site.site_root, "rophako/www"]:
abspath = os.path.abspath("{}/{}".format(root, path))
if os.path.isfile(abspath):
return send_file(abspath)

View File

@ -12,7 +12,7 @@ import redis
import json
import time
import config
from rophako.settings import Config
from rophako.log import logger
redis_client = None
@ -111,7 +111,7 @@ def mkpath(document):
if document.endswith(".json"):
# Let's not do that.
raise Exception("mkpath: document path already includes .json extension!")
return "{}/{}.json".format(config.DB_ROOT, str(document))
return "{}/{}.json".format(Config.db.db_root, str(document))
def read_json(path):
@ -137,6 +137,7 @@ def read_json(path):
data = json.loads(text)
except:
logger.error("Couldn't decode JSON data from {}".format(path))
logger.error(text)
data = None
return data
@ -179,16 +180,16 @@ def get_redis():
global redis_client
if not redis_client:
redis_client = redis.StrictRedis(
host = config.REDIS_HOST,
port = config.REDIS_PORT,
db = config.REDIS_DB,
host = Config.db.redis_host,
port = Config.db.redis_port,
db = Config.db.redis_db,
)
return redis_client
def set_cache(key, value, expires=None):
"""Set a key in the Redis cache."""
key = config.REDIS_PREFIX + key
key = Config.db.redis_prefix + key
try:
client = get_redis()
client.set(key, json.dumps(value))
@ -202,7 +203,7 @@ def set_cache(key, value, expires=None):
def get_cache(key):
"""Get a cached item."""
key = config.REDIS_PREFIX + key
key = Config.db.redis_prefix + key
value = None
try:
client = get_redis()
@ -217,6 +218,6 @@ def get_cache(key):
def del_cache(key):
"""Delete a cached item."""
key = config.REDIS_PREFIX + key
key = Config.db.redis_prefix + key
client = get_redis()
client.delete(key)

View File

@ -7,7 +7,7 @@ from __future__ import print_function
from flask import g, request
import logging
import config
from rophako.settings import Config
class LogHandler(logging.Handler):
"""A custom logging handler."""
@ -29,7 +29,7 @@ handler.setFormatter(logging.Formatter("[%(asctime)s] [%(levelname)s] $prefix$%(
logger.addHandler(handler)
# Log level.
if config.DEBUG:
if Config.site.debug == "true":
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
logger.setLevel(logging.INFO)

View File

@ -8,7 +8,7 @@ import re
import glob
import os
import config
from rophako.settings import Config
import rophako.jsondb as JsonDB
from rophako.log import logger
@ -206,7 +206,7 @@ def list_avatars():
# Load avatars from both locations. We check the built-in set first,
# so if you have matching names in your local site those will override.
"rophako/www/static/avatars/*.*",
os.path.join(config.SITE_ROOT, "static", "avatars", "*.*"),
os.path.join(Config.site.site_root, "static", "avatars", "*.*"),
]
for path in paths:
for filename in glob.glob(path):
@ -228,4 +228,4 @@ def get_next_id(index):
# Sanity check!
if next_id in index:
raise Exception("Failed to get_next_id for the blog. Chosen ID is still in the index!")
return next_id
return next_id

View File

@ -2,7 +2,7 @@
"""Commenting models."""
from flask import g, url_for
from flask import url_for
import time
import hashlib
import urllib
@ -10,7 +10,7 @@ import random
import re
import sys
import config
from rophako.settings import Config
import rophako.jsondb as JsonDB
import rophako.model.user as User
import rophako.model.emoticons as Emoticons
@ -58,7 +58,7 @@ def add_comment(thread, uid, name, subject, message, url, time, ip, image=None):
# Send the e-mail to the site admins.
send_email(
to=config.NOTIFY_ADDRESS,
to=Config.site.notify_address,
subject="New comment: {}".format(subject),
message="""{name} has left a comment on: {subject}
@ -230,7 +230,7 @@ def gravatar(email):
"""Generate a Gravatar link for an email address."""
if "@" in email:
# Default avatar?
default = config.COMMENT_DEFAULT_AVATAR
default = Config.comment.default_avatar
# Construct the URL.
params = {

View File

@ -2,14 +2,12 @@
"""Emoticon models."""
from flask import g, url_for
import os
import codecs
import json
import re
import config
import rophako.jsondb as JsonDB
from rophako.settings import Config
from rophako.log import logger
@ -18,7 +16,7 @@ _cache = {}
def load_theme():
"""Pre-load and cache the emoticon theme. This happens on startup."""
theme = config.EMOTICON_THEME
theme = Config.emoticons.theme
global _cache
# Cached?
@ -26,13 +24,13 @@ def load_theme():
return _cache
# Only if the theme file exists.
settings = os.path.join(config.EMOTICON_ROOT_PRIVATE, theme, "emoticons.json")
settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json")
if not os.path.isfile(settings):
logger.error("Failed to load smiley theme {}: not found!")
# Try the default (tango).
theme = "tango"
settings = os.path.join(config.EMOTICON_ROOT_PRIVATE, theme, "emoticons.json")
settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json")
if os.path.isfile(settings):
logger.info("Falling back to default theme: tango")
else:
@ -71,11 +69,11 @@ def render(message):
if trigger in message:
# Substitute it.
sub = """<img src="{url}" alt="{trigger}" title="{trigger}">""".format(
url="/static/smileys/{}/{}".format(config.EMOTICON_THEME, img),
url="/static/smileys/{}/{}".format(Config.emoticons.theme, img),
trigger=trigger,
)
pattern = r'([^A-Za-z0-9:\-]|^){}([^A-Za-z0-9:\-]|$)'.format(re.escape(trigger))
result = r'\1{}\2'.format(sub)
message = re.sub(pattern, result, message)
return message
return message

View File

@ -10,16 +10,16 @@ from PIL import Image
import hashlib
import random
import config
from rophako.settings import Config
import rophako.jsondb as JsonDB
from rophako.utils import sanitize_name, remote_addr
from rophako.log import logger
# Maps the friendly names of photo sizes with their pixel values from config.
PHOTO_SCALES = dict(
large=config.PHOTO_WIDTH_LARGE,
thumb=config.PHOTO_WIDTH_THUMB,
avatar=config.PHOTO_WIDTH_AVATAR,
large=int(Config.photo.width_large),
thumb=int(Config.photo.width_thumb),
avatar=int(Config.photo.width_avatar),
)
@ -57,7 +57,6 @@ def list_albums():
def get_album(name):
"""Get details about an album."""
index = get_index()
result = []
if not name in index["albums"]:
return None
@ -79,7 +78,7 @@ def rename_album(old_name, new_name):
Returns True on success, False if the new name conflicts with another
album's name."""
old_name = sanitize_name(old_name)
newname = sanitize_name(new_name)
new_name = sanitize_name(new_name)
index = get_index()
# New name is unique?
@ -196,7 +195,7 @@ def get_photo(key):
def get_image_dimensions(pic):
"""Use PIL to get the image's true dimensions."""
filename = os.path.join(config.PHOTO_ROOT_PRIVATE, pic["large"])
filename = os.path.join(Config.photo.root_private, pic["large"])
img = Image.open(filename)
return img.size
@ -233,11 +232,11 @@ def crop_photo(key, x, y, length):
for size in ["thumb", "avatar"]:
pic = index["albums"][album][key][size]
logger.debug("Delete {} size: {}".format(size, pic))
os.unlink(os.path.join(config.PHOTO_ROOT_PRIVATE, pic))
os.unlink(os.path.join(Config.photo.root_private, pic))
# Regenerate all the thumbnails.
large = index["albums"][album][key]["large"]
source = os.path.join(config.PHOTO_ROOT_PRIVATE, large)
source = os.path.join(Config.photo.root_private, large)
for size in ["thumb", "avatar"]:
pic = resize_photo(source, size, crop=dict(
x=x,
@ -305,7 +304,7 @@ def rotate_photo(key, rotate):
new_names = dict()
for size in ["large", "thumb", "avatar"]:
fname = os.path.join(config.PHOTO_ROOT_PRIVATE, photo[size])
fname = os.path.join(Config.photo.root_private, photo[size])
logger.info("Rotating image {} by {} degrees.".format(fname, degrees))
# Give it a new name.
@ -315,7 +314,7 @@ def rotate_photo(key, rotate):
img = Image.open(fname)
img = img.rotate(degrees)
img.save(os.path.join(config.PHOTO_ROOT_PRIVATE, outfile))
img.save(os.path.join(Config.photo.root_private, outfile))
# Delete the old name.
os.unlink(fname)
@ -339,7 +338,7 @@ def delete_photo(key):
# Delete all the images.
for size in ["large", "thumb", "avatar"]:
logger.info("Delete: {}".format(photo[size]))
fname = os.path.join(config.PHOTO_ROOT_PRIVATE, photo[size])
fname = os.path.join(Config.photo.root_private, photo[size])
if os.path.isfile(fname):
os.unlink(fname)
@ -428,7 +427,7 @@ def upload_from_pc(request):
if not allowed_filetype(upload.filename):
return dict(success=False, error="Unsupported file extension.")
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
tempfile = "{}/rophako-photo-{}.{}".format(Config.site.tempdir, int(time.time()), filetype)
logger.debug("Save incoming photo to: {}".format(tempfile))
upload.save(tempfile)
@ -461,7 +460,7 @@ def upload_from_www(form):
# Make a temp filename for it.
filetype = url.rsplit(".", 1)[1]
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
tempfile = "{}/rophako-photo-{}.{}".format(Config.site.tempdir, int(time.time()), filetype)
logger.debug("Save incoming photo to: {}".format(tempfile))
# Grab the file.
@ -500,7 +499,7 @@ def process_photo(form, filename):
album = sanitize_name(album)
if album == "":
logger.warning("Album name didn't pass sanitization! Fall back to default album name.")
album = config.PHOTO_DEFAULT_ALBUM
album = Config.photo.default_album
# Make up a unique public key for this set of photos.
key = random_hash()
@ -574,7 +573,7 @@ def resize_photo(filename, size, crop=None):
# Make up a unique filename.
outfile = random_name(filetype)
target = os.path.join(config.PHOTO_ROOT_PRIVATE, outfile)
target = os.path.join(Config.photo.root_private, outfile)
logger.debug("Output file for {} scale: {}".format(size, target))
# Get the image's dimensions.
@ -679,7 +678,7 @@ def random_name(filetype):
"""Get a random available file name to save a new photo."""
filetype = filetype.lower()
outfile = random_hash() + "." + filetype
while os.path.isfile(os.path.join(config.PHOTO_ROOT_PRIVATE, outfile)):
while os.path.isfile(os.path.join(Config.photo.root_private, outfile)):
outfile = random_hash() + "." + filetype
return outfile

View File

@ -5,7 +5,7 @@
import bcrypt
import time
import config
from rophako.settings import Config
import rophako.jsondb as JsonDB
import rophako.model.photo as Photo
from rophako.log import logger
@ -147,7 +147,7 @@ def exists(uid=None, username=None):
def hash_password(password):
return bcrypt.hashpw(str(password).encode("utf-8"), bcrypt.gensalt(config.BCRYPT_ITERATIONS)).decode("utf-8")
return bcrypt.hashpw(str(password).encode("utf-8"), bcrypt.gensalt(int(Config.security.bcrypt_iterations))).decode("utf-8")
def check_auth(username, password):
@ -172,4 +172,4 @@ def get_next_uid():
uid = 1
while exists(uid=uid):
uid += 1
return uid
return uid

View File

@ -14,8 +14,8 @@ import rophako.model.emoticons as Emoticons
from rophako.utils import (template, render_markdown, pretty_time,
login_required, remote_addr)
from rophako.plugin import load_plugin
from rophako.settings import Config
from rophako.log import logger
from config import *
mod = Blueprint("blog", __name__, url_prefix="/blog")
load_plugin("rophako.modules.comment")
@ -34,16 +34,16 @@ def archive():
groups = dict()
friendly_months = dict()
for post_id, data in index.items():
time = datetime.datetime.fromtimestamp(data["time"])
date = time.strftime("%Y-%m")
ts = datetime.datetime.fromtimestamp(data["time"])
date = ts.strftime("%Y-%m")
if not date in groups:
groups[date] = dict()
friendly = time.strftime("%B %Y")
friendly = ts.strftime("%B %Y")
friendly_months[date] = friendly
# Get author's profile && Pretty-print the time.
data["profile"] = User.get_user(uid=data["author"])
data["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, data["time"])
data["pretty_time"] = pretty_time(Config.blog.time_format, data["time"])
groups[date][post_id] = data
# Sort by calendar month.
@ -101,10 +101,10 @@ def entry(fid):
# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
post["photo"] = User.get_picture(uid=post["author"])
post["photo_url"] = PHOTO_ROOT_PUBLIC
post["photo_url"] = Config.photo.root_public
# Pretty-print the time.
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
post["pretty_time"] = pretty_time(Config.blog.time_format, post["time"])
# Count the comments for this post
post["comment_count"] = Comment.count_comments("blog-{}".format(post_id))
@ -153,9 +153,9 @@ def update():
format="markdown",
avatar="",
categories="",
privacy=BLOG_DEFAULT_PRIVACY,
privacy=Config.blog.default_privacy,
emoticons=True,
comments=BLOG_ALLOW_COMMENTS,
comments=Config.blog.allow_comments,
month="",
day="",
year="",
@ -328,14 +328,14 @@ def rss():
today = time.strftime(rss_time, time.gmtime())
xml_add_text_tags(doc, channel, [
["title", RSS_TITLE],
["link", RSS_LINK],
["description", RSS_DESCRIPTION],
["language", RSS_LANGUAGE],
["copyright", RSS_COPYRIGHT],
["title", Config.blog.title],
["link", Config.blog.link],
["description", Config.blog.description],
["language", Config.blog.language],
["copyright", Config.blog.copyright],
["pubDate", today],
["lastBuildDate", today],
["webmaster", RSS_WEBMASTER],
["webmaster", Config.blog.webmaster],
])
######
@ -345,12 +345,12 @@ def rss():
image = doc.createElement("image")
channel.appendChild(image)
xml_add_text_tags(doc, image, [
["title", RSS_IMAGE_TITLE],
["url", RSS_IMAGE_URL],
["link", RSS_LINK],
["width", RSS_IMAGE_WIDTH],
["height", RSS_IMAGE_HEIGHT],
["description", RSS_IMAGE_DESCRIPTION],
["title", Config.blog.image_title],
["url", Config.blog.image_url],
["link", Config.blog.link],
["width", Config.blog.image_width],
["height", Config.blog.image_height],
["description", Config.blog.image_description],
])
######
@ -359,7 +359,7 @@ def rss():
index = Blog.get_index()
posts = get_index_posts(index)
for post_id in posts[:BLOG_ENTRIES_PER_RSS]:
for post_id in posts[:int(Config.blog.entries_per_feed)]:
post = Blog.get_entry(post_id)
item = doc.createElement("item")
channel.appendChild(item)
@ -432,8 +432,8 @@ def partial_index():
# Handle the offsets, and get those for the "older" and "earlier" posts.
# "earlier" posts count down (towards index 0), "older" counts up.
g.info["offset"] = offset
g.info["earlier"] = offset - BLOG_ENTRIES_PER_PAGE if offset > 0 else 0
g.info["older"] = offset + BLOG_ENTRIES_PER_PAGE
g.info["earlier"] = offset - int(Config.blog.entries_per_page) if offset > 0 else 0
g.info["older"] = offset + int(Config.blog.entries_per_page)
if g.info["earlier"] < 0:
g.info["earlier"] = 0
if g.info["older"] < 0 or g.info["older"] > len(posts):
@ -446,7 +446,7 @@ def partial_index():
# Load the selected posts.
selected = []
stop = offset + BLOG_ENTRIES_PER_PAGE
stop = offset + int(Config.blog.entries_per_page)
if stop > len(posts): stop = len(posts)
index = 1 # Let each post know its position on-page.
for i in range(offset, stop):
@ -468,9 +468,9 @@ def partial_index():
# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
post["photo"] = User.get_picture(uid=post["author"])
post["photo_url"] = PHOTO_ROOT_PUBLIC
post["photo_url"] = Config.photo.root_public
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
post["pretty_time"] = pretty_time(Config.blog.time_format, post["time"])
# Count the comments for this post
post["comment_count"] = Comment.count_comments("blog-{}".format(post_id))

View File

@ -2,8 +2,7 @@
"""Endpoints for the commenting subsystem."""
from flask import Blueprint, g, request, redirect, url_for, session, flash
import re
from flask import Blueprint, g, request, redirect, url_for, flash
import time
import rophako.model.user as User
@ -11,8 +10,7 @@ import rophako.model.comment as Comment
from rophako.utils import (template, pretty_time, login_required, sanitize_name,
remote_addr)
from rophako.plugin import load_plugin
from rophako.log import logger
from config import *
from rophako.settings import Config
mod = Blueprint("comment", __name__, url_prefix="/comments")
load_plugin("rophako.modules.emoticons")
@ -71,7 +69,7 @@ def preview():
# Gravatar.
g.info["gravatar"] = gravatar
g.info["preview"] = Comment.format_message(form["message"])
g.info["pretty_time"] = pretty_time(COMMENT_TIME_FORMAT, time.time())
g.info["pretty_time"] = pretty_time(Config.comment.time_format, time.time())
g.info.update(form)
return template("comment/preview.html")
@ -191,7 +189,7 @@ def partial_index(thread, subject, header=True):
comment["image"] = avatar
# Add the pretty time.
comment["pretty_time"] = pretty_time(COMMENT_TIME_FORMAT, comment["time"])
comment["pretty_time"] = pretty_time(Config.comment.time_format, comment["time"])
# Format the message for display.
comment["formatted_message"] = Comment.format_message(comment["message"])
@ -203,7 +201,7 @@ def partial_index(thread, subject, header=True):
g.info["subject"] = subject
g.info["url"] = request.url
g.info["comments"] = sorted_comments
g.info["photo_url"] = PHOTO_ROOT_PUBLIC
g.info["photo_url"] = Config.photo.root_public
return template("comment/index.inc.html")

View File

@ -5,7 +5,7 @@
from flask import Blueprint, request, redirect, url_for, flash
from rophako.utils import template, send_email, remote_addr
from config import *
from rophako.settings import Config
mod = Blueprint("contact", __name__, url_prefix="/contact")
@ -42,9 +42,9 @@ def send():
# Send the e-mail.
send_email(
to=NOTIFY_ADDRESS,
to=Config.site.notify_address,
reply_to=reply_to,
subject="Contact Form on {}: {}".format(SITE_NAME, subject),
subject="Contact Form on {}: {}".format(Config.site.site_name, subject),
message="""A visitor to {site_name} has sent you a message!
IP Address: {ip}
@ -55,7 +55,7 @@ E-mail: {email}
Subject: {subject}
{message}""".format(
site_name=SITE_NAME,
site_name=Config.site.site_name,
ip=remote_addr(),
ua=request.user_agent.string,
referer=request.headers.get("Referer", ""),

View File

@ -6,8 +6,7 @@ from flask import Blueprint, g
import rophako.model.emoticons as Emoticons
from rophako.utils import template
from rophako.log import logger
from config import *
from rophako.settings import Config
mod = Blueprint("emoticons", __name__, url_prefix="/emoticons")
@ -24,7 +23,7 @@ def index():
"triggers": theme["map"][img],
})
g.info["theme"] = EMOTICON_THEME
g.info["theme"] = Config.emoticons.theme
g.info["theme_name"] = theme["name"]
g.info["smileys"] = smileys
return template("emoticons/index.html")
return template("emoticons/index.html")

View File

@ -2,15 +2,14 @@
"""Endpoints for the photo albums."""
from flask import Blueprint, g, request, redirect, url_for, session, flash
from flask import Blueprint, g, request, redirect, url_for, flash
import rophako.model.user as User
import rophako.model.photo as Photo
from rophako.utils import (template, pretty_time, render_markdown,
login_required, ajax_response)
from rophako.plugin import load_plugin
from rophako.log import logger
from config import *
from rophako.settings import Config
mod = Blueprint("photo", __name__, url_prefix="/photos")
load_plugin("rophako.modules.comment")
@ -68,7 +67,7 @@ def view_photo(key):
g.info["photo"] = photo
g.info["photo"]["key"] = key
g.info["photo"]["pretty_time"] = pretty_time(PHOTO_TIME_FORMAT, photo["uploaded"])
g.info["photo"]["pretty_time"] = pretty_time(Config.photo.time_format, photo["uploaded"])
g.info["photo"]["markdown"] = render_markdown(photo.get("description", ""))
return template("photos/view.html")
@ -125,10 +124,10 @@ def upload():
g.info["album_list"] = [
"My Photos", # the default
]
g.info["selected"] = PHOTO_DEFAULT_ALBUM
g.info["selected"] = Config.photo.default_album
albums = Photo.list_albums()
if len(albums):
g.info["album_list"] = [ album["name"] for album in albums ]
g.info["album_list"] = [ x["name"] for x in albums ]
g.info["selected"] = albums[0]
return template("photos/upload.html")

View File

@ -26,4 +26,4 @@ def load_plugin(name, as_blueprint=True, template_path=None):
module_path = name.replace(".", "/")
template_path = os.path.join(module_path, "templates")
BLUEPRINT_PATHS.append(template_path)
BLUEPRINT_PATHS.append(template_path)

60
rophako/settings.py Normal file
View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
import os
import datetime
from attrdict import AttrDict
from ConfigParser import ConfigParser
from rophako.plugin import load_plugin
# Get the base directory of the git root.
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
# https://github.com/bcj/AttrDict/issues/20
if not hasattr(AttrDict, "copy"):
setattr(AttrDict, "copy", lambda self: self._mapping.copy())
class ConfigHandler(object):
settings = None
def load_settings(self):
"""Load the settings files and make them available in the global config."""
self.settings = ConfigParser(dict_type=AttrDict)
# Set dynamic default variables.
self.settings.set("DEFAULT", "_basedir", basedir)
self.settings.set("DEFAULT", "_year", str(datetime.datetime.now().strftime("%Y")))
# Read the defaults and then apply the custom settings on top.
self.settings.read(["defaults.ini", "settings.ini"])
def print_settings(self):
"""Pretty-print the contents of the configuration as JSON."""
for section in self.settings.sections():
print "[{}]".format(section)
for opt in self.settings.options(section):
print "{} = {}".format(opt, repr(self.settings.get(section, opt)))
print ""
def load_plugins(self):
"""Load all the plugins specified by the config file."""
for plugin in self.plugins.blueprints.split("\n"):
plugin = plugin.strip()
if not plugin:
continue
load_plugin(plugin)
for custom in self.plugins.custom.split("\n"):
custom = custom.strip()
if not custom:
continue
load_plugin(custom, as_blueprint=False)
def __getattr__(self, section):
"""Attribute access for the config object.
You can access config settings via Config.<section>.<name>, for example
Config.site.notify_email and Config.blog.posts_per_page. All results are
returned as strings per ConfigParser, so cast them if you need to."""
return AttrDict(dict(self.settings.items(section)))
Config = ConfigHandler()

View File

@ -14,7 +14,7 @@ import json
import urlparse
from rophako.log import logger
from config import *
from rophako.settings import Config
def login_required(f):
@ -167,13 +167,13 @@ def render_markdown(body, html_escape=True, extensions=None, blacklist=None):
def send_email(to, subject, message, sender=None, reply_to=None):
"""Send an e-mail out."""
if sender is None:
sender = MAIL_SENDER
sender = Config.mail.sender
if type(to) != list:
to = [to]
logger.info("Send email to {}".format(to))
if MAIL_METHOD == "smtp":
if Config.mail.method == "smtp":
# Send mail with SMTP.
for email in to:
# Construct the mail headers.
@ -186,7 +186,7 @@ def send_email(to, subject, message, sender=None, reply_to=None):
headers.append("Subject: {}".format(subject))
# Prepare the mail for transport.
server = smtplib.SMTP(MAIL_SERVER, MAIL_PORT)
server = smtplib.SMTP(Config.mail.server, Config.mail.port)
msg = "\n".join(headers) + "\n\n" + message
server.sendmail(sender, email, msg)
server.quit()