Browse Source

Move to new settings system

pull/2/head
Noah Petherbridge 5 years ago
parent
commit
ff75921129

+ 1
- 1
.gitignore View File

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

# Compiled Python
*.pyc

+ 180
- 0
defaults.ini 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 =

+ 2
- 2
kirsle_legacy.py 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,
},
}))

+ 1
- 1
requirements.txt View File

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

+ 11
- 8
rophako/app.py 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)

+ 9
- 8
rophako/jsondb.py 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)

+ 3
- 3
rophako/log.py 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)

+ 3
- 3
rophako/model/blog.py 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

+ 4
- 4
rophako/model/comment.py 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 = {

+ 6
- 8
rophako/model/emoticons.py 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

+ 16
- 17
rophako/model/photo.py 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


+ 3
- 3
rophako/model/user.py 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

+ 27
- 27
rophako/modules/blog/__init__.py 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))

+ 5
- 7
rophako/modules/comment/__init__.py 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")



+ 4
- 4
rophako/modules/contact/__init__.py 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", ""),

+ 3
- 4
rophako/modules/emoticons/__init__.py 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")

+ 5
- 6
rophako/modules/photo/__init__.py 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")

+ 1
- 1
rophako/plugin.py 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
- 0
rophako/settings.py 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()

+ 4
- 4
rophako/utils.py 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()

Loading…
Cancel
Save