Add the web blog views/models/controllers
This commit is contained in:
parent
80c20ec87b
commit
f9c2481499
|
@ -31,4 +31,12 @@ DB_ROOT = "db"
|
||||||
REDIS_HOST = "localhost"
|
REDIS_HOST = "localhost"
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
REDIS_DB = 0
|
REDIS_DB = 0
|
||||||
REDIS_PREFIX = "rophako:"
|
REDIS_PREFIX = "rophako:"
|
||||||
|
|
||||||
|
# Blog settings
|
||||||
|
BLOG_ENTRIES_PER_PAGE = 5 # Number of entries to show per page
|
||||||
|
BLOG_ENTRIES_PER_RSS = 5 # The same, but for the RSS feed
|
||||||
|
BLOG_DEFAULT_CATEGORY = "Uncategorized"
|
||||||
|
BLOG_DEFAULT_PRIVACY = "public"
|
||||||
|
BLOG_TIME_FORMAT = "%A, %B %d %Y @ %I:%M:%S %p" # "Weekday, Month dd yyyy @ hh:mm:ss AM"
|
||||||
|
BLOG_ALLOW_COMMENTS = True
|
|
@ -17,8 +17,10 @@ app.secret_key = config.SECRET_KEY
|
||||||
# Load all the blueprints!
|
# Load all the blueprints!
|
||||||
from rophako.modules.admin import mod as AdminModule
|
from rophako.modules.admin import mod as AdminModule
|
||||||
from rophako.modules.account import mod as AccountModule
|
from rophako.modules.account import mod as AccountModule
|
||||||
|
from rophako.modules.blog import mod as BlogModule
|
||||||
app.register_blueprint(AdminModule)
|
app.register_blueprint(AdminModule)
|
||||||
app.register_blueprint(AccountModule)
|
app.register_blueprint(AccountModule)
|
||||||
|
app.register_blueprint(BlogModule)
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -28,6 +30,7 @@ app.jinja_loader = jinja2.ChoiceLoader([
|
||||||
])
|
])
|
||||||
|
|
||||||
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.before_request
|
@app.before_request
|
||||||
|
@ -85,7 +88,6 @@ def before_request():
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def after_request():
|
def after_request():
|
||||||
"""Called just before render_template. Inject g.info into the template vars."""
|
"""Called just before render_template. Inject g.info into the template vars."""
|
||||||
g.info["time_elapsed"] = "%.03f" % (time.time() - g.info["time"])
|
|
||||||
return g.info
|
return g.info
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,14 +102,13 @@ def catchall(path):
|
||||||
if os.path.isfile(abspath):
|
if os.path.isfile(abspath):
|
||||||
return send_file(abspath)
|
return send_file(abspath)
|
||||||
elif not "." in path and os.path.isfile(abspath + ".html"):
|
elif not "." in path and os.path.isfile(abspath + ".html"):
|
||||||
return render_template(path + ".html")
|
return rophako.utils.template(path + ".html")
|
||||||
|
|
||||||
return not_found("404")
|
return not_found("404")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
print "INDEX PAGE"
|
|
||||||
return catchall("index")
|
return catchall("index")
|
||||||
|
|
||||||
|
|
||||||
|
|
230
rophako/model/blog.py
Normal file
230
rophako/model/blog.py
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Blog models."""
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import glob
|
||||||
|
|
||||||
|
import config
|
||||||
|
import rophako.jsondb as JsonDB
|
||||||
|
from rophako.log import logger
|
||||||
|
|
||||||
|
def get_index():
|
||||||
|
"""Get the blog index.
|
||||||
|
|
||||||
|
The index is the cache of available blog posts. It has the format:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
'post_id': {
|
||||||
|
fid: Friendly ID for the blog post (for URLs)
|
||||||
|
time: epoch time of the post
|
||||||
|
sticky: the stickiness of the post (shows first on global views)
|
||||||
|
author: the author user ID of the post
|
||||||
|
categories: [ list of categories ]
|
||||||
|
privacy: the privacy setting
|
||||||
|
subject: the post subject
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Index doesn't exist?
|
||||||
|
if not JsonDB.exists("blog/index"):
|
||||||
|
return {}
|
||||||
|
db = JsonDB.get("blog/index")
|
||||||
|
|
||||||
|
# Hide any private posts if we aren't logged in.
|
||||||
|
if not g.info["session"]["login"]:
|
||||||
|
for post_id, data in db.iteritems():
|
||||||
|
if data["privacy"] == "private":
|
||||||
|
del db[post_id]
|
||||||
|
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def __get_categories():
|
||||||
|
"""Get the blog categories cache.
|
||||||
|
|
||||||
|
The category cache is in the following format:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
'category_name': {
|
||||||
|
'post_id': 'friendly_id',
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Index doesn't exist?
|
||||||
|
if not JsonDB.exists("blog/tags"):
|
||||||
|
return {}
|
||||||
|
return JsonDB.get("blog/tags")
|
||||||
|
|
||||||
|
|
||||||
|
def get_entry(post_id):
|
||||||
|
"""Load a full blog entry."""
|
||||||
|
if not JsonDB.exists("blog/entries/{}".format(post_id)):
|
||||||
|
return None
|
||||||
|
|
||||||
|
db = JsonDB.get("blog/entries/{}".format(post_id))
|
||||||
|
|
||||||
|
# If no FID, set it to the ID.
|
||||||
|
if len(db["fid"]) == 0:
|
||||||
|
db["fid"] = str(post_id)
|
||||||
|
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def post_entry(post_id, fid, epoch, author, subject, avatar, categories,
|
||||||
|
privacy, ip, emoticons, comments, body):
|
||||||
|
"""Post (or update) a blog entry."""
|
||||||
|
|
||||||
|
# Fetch the index.
|
||||||
|
index = get_index()
|
||||||
|
|
||||||
|
# Editing an existing post?
|
||||||
|
if not post_id:
|
||||||
|
post_id = get_next_id(index)
|
||||||
|
|
||||||
|
logger.debug("Posting blog post ID {}".format(post_id))
|
||||||
|
|
||||||
|
# Get a unique friendly ID.
|
||||||
|
if not fid:
|
||||||
|
# The default friendly ID = the subject.
|
||||||
|
fid = subject.lower()
|
||||||
|
fid = re.sub(r'[^A-Za-z0-9]', '-', fid)
|
||||||
|
fid = re.sub(r'\-+', '-', fid)
|
||||||
|
fid = fid.strip("-")
|
||||||
|
logger.debug("Chosen friendly ID: {}".format(fid))
|
||||||
|
|
||||||
|
# Make sure the friendly ID is unique!
|
||||||
|
if len(fid):
|
||||||
|
test = fid
|
||||||
|
loop = 1
|
||||||
|
logger.debug("Verifying the friendly ID is unique: {}".format(fid))
|
||||||
|
while True:
|
||||||
|
collision = False
|
||||||
|
|
||||||
|
for k, v in index.iteritems():
|
||||||
|
# Skip the same post, for updates.
|
||||||
|
if k == post_id: continue
|
||||||
|
|
||||||
|
if v["fid"] == test:
|
||||||
|
# Not unique.
|
||||||
|
loop += 1
|
||||||
|
test = fid + "_" + unicode(loop)
|
||||||
|
collision = True
|
||||||
|
logger.debug("Collision with existing post {}: {}".format(k, v["fid"]))
|
||||||
|
break
|
||||||
|
|
||||||
|
# Was there a collision?
|
||||||
|
if collision:
|
||||||
|
continue # Try again.
|
||||||
|
|
||||||
|
# Nope!
|
||||||
|
break
|
||||||
|
fid = test
|
||||||
|
|
||||||
|
# Write the post.
|
||||||
|
JsonDB.commit("blog/entries/{}".format(post_id), dict(
|
||||||
|
fid = fid,
|
||||||
|
ip = ip,
|
||||||
|
time = epoch or int(time.time()),
|
||||||
|
categories = categories,
|
||||||
|
sticky = False, # TODO: implement sticky
|
||||||
|
comments = comments,
|
||||||
|
emoticons = emoticons,
|
||||||
|
avatar = avatar,
|
||||||
|
privacy = privacy or "public",
|
||||||
|
author = author,
|
||||||
|
subject = subject,
|
||||||
|
body = body,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Update the index cache.
|
||||||
|
index[post_id] = dict(
|
||||||
|
fid = fid,
|
||||||
|
time = epoch or int(time.time()),
|
||||||
|
categories = categories,
|
||||||
|
sticky = False, # TODO
|
||||||
|
author = author,
|
||||||
|
privacy = privacy or "public",
|
||||||
|
subject = subject,
|
||||||
|
)
|
||||||
|
JsonDB.commit("blog/index", index)
|
||||||
|
|
||||||
|
return post_id, fid
|
||||||
|
|
||||||
|
|
||||||
|
def delete_entry(post_id):
|
||||||
|
"""Remove a blog entry."""
|
||||||
|
# Fetch the blog information.
|
||||||
|
index = get_index()
|
||||||
|
post = get_entry(post_id)
|
||||||
|
if post is None:
|
||||||
|
logger.warning("Can't delete post {}, it doesn't exist!".format(post_id))
|
||||||
|
|
||||||
|
# Delete the post.
|
||||||
|
JsonDB.delete("blog/entries/{}".format(post_id))
|
||||||
|
|
||||||
|
# Update the index cache.
|
||||||
|
del index[str(post_id)] # Python JSON dict keys must be strings, never ints
|
||||||
|
JsonDB.commit("blog/index", index)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_id(fid):
|
||||||
|
"""Resolve a friendly ID to the blog ID number."""
|
||||||
|
index = get_index()
|
||||||
|
|
||||||
|
# If the ID is all numeric, it's the blog post ID directly.
|
||||||
|
if re.match(r'^\d+$', fid):
|
||||||
|
if fid in index:
|
||||||
|
return int(fid)
|
||||||
|
else:
|
||||||
|
logger.error("Tried resolving blog post ID {} as an EntryID, but it wasn't there!".format(fid))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# It's a friendly ID. Scan for it.
|
||||||
|
for post_id, data in index.iteritems():
|
||||||
|
if data["fid"] == fid:
|
||||||
|
return int(post_id)
|
||||||
|
|
||||||
|
logger.error("Friendly post ID {} wasn't found!".format(fid))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def list_avatars():
|
||||||
|
"""Get a list of all the available blog avatars."""
|
||||||
|
avatars = set()
|
||||||
|
paths = [
|
||||||
|
# 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/*.*",
|
||||||
|
"site/www/static/avatars/*.*",
|
||||||
|
]
|
||||||
|
for path in paths:
|
||||||
|
for filename in glob.glob(path):
|
||||||
|
filename = filename.split("/")[-1]
|
||||||
|
avatars.add(filename)
|
||||||
|
|
||||||
|
return sorted(avatars, key=lambda x: x.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_id(index):
|
||||||
|
"""Get the next free ID for a blog post."""
|
||||||
|
logger.debug("Getting next available blog ID number")
|
||||||
|
sort = sorted(index.keys(), key=lambda x: int(x))
|
||||||
|
logger.debug("Highest post ID is: {}".format(sort[-1]))
|
||||||
|
next_id = int(sort[-1]) + 1
|
||||||
|
|
||||||
|
# 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
|
300
rophako/modules/blog.py
Normal file
300
rophako/modules/blog.py
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Endpoints for the web blog."""
|
||||||
|
|
||||||
|
from flask import Blueprint, g, request, redirect, url_for, session, flash
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
import rophako.model.user as User
|
||||||
|
import rophako.model.blog as Blog
|
||||||
|
from rophako.utils import template, pretty_time, admin_required
|
||||||
|
from rophako.log import logger
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
mod = Blueprint("blog", __name__, url_prefix="/blog")
|
||||||
|
|
||||||
|
@mod.route("/")
|
||||||
|
def index():
|
||||||
|
return template("blog/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/category/<category>")
|
||||||
|
def category(category):
|
||||||
|
g.info["url_category"] = category
|
||||||
|
return template("blog/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/entry/<fid>")
|
||||||
|
def entry(fid):
|
||||||
|
"""Endpoint to view a specific blog entry."""
|
||||||
|
|
||||||
|
# Resolve the friendly ID to a real ID.
|
||||||
|
post_id = Blog.resolve_id(fid)
|
||||||
|
if not post_id:
|
||||||
|
flash("That blog post wasn't found.")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
# Look up the post.
|
||||||
|
post = Blog.get_entry(post_id)
|
||||||
|
post["post_id"] = post_id
|
||||||
|
|
||||||
|
# Get the author's information.
|
||||||
|
post["profile"] = User.get_user(uid=post["author"])
|
||||||
|
|
||||||
|
# Pretty-print the time.
|
||||||
|
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
||||||
|
|
||||||
|
# TODO: count the comments for this post
|
||||||
|
post["comment_count"] = 0
|
||||||
|
|
||||||
|
g.info["post"] = post
|
||||||
|
return template("blog/entry.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/entry")
|
||||||
|
@mod.route("/index")
|
||||||
|
def dummy():
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/update", methods=["GET", "POST"])
|
||||||
|
@admin_required
|
||||||
|
def update():
|
||||||
|
"""Post/edit a blog entry."""
|
||||||
|
|
||||||
|
# Get our available avatars.
|
||||||
|
g.info["avatars"] = Blog.list_avatars()
|
||||||
|
|
||||||
|
# Default vars.
|
||||||
|
g.info.update(dict(
|
||||||
|
post_id="",
|
||||||
|
fid="",
|
||||||
|
author=g.info["session"]["uid"],
|
||||||
|
subject="",
|
||||||
|
body="",
|
||||||
|
avatar="",
|
||||||
|
categories="",
|
||||||
|
privacy=BLOG_DEFAULT_PRIVACY,
|
||||||
|
emoticons=True,
|
||||||
|
comments=BLOG_ALLOW_COMMENTS,
|
||||||
|
month="",
|
||||||
|
day="",
|
||||||
|
year="",
|
||||||
|
hour="",
|
||||||
|
min="",
|
||||||
|
sec="",
|
||||||
|
preview=False,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Editing an existing post?
|
||||||
|
post_id = request.args.get("id", None)
|
||||||
|
if post_id:
|
||||||
|
post_id = Blog.resolve_id(post_id)
|
||||||
|
if post_id:
|
||||||
|
logger.info("Editing existing blog post {}".format(post_id))
|
||||||
|
post = Blog.get_entry(post_id)
|
||||||
|
g.info["post_id"] = post_id
|
||||||
|
g.info["post"] = post
|
||||||
|
|
||||||
|
# Copy fields.
|
||||||
|
for field in ["author", "fid", "subject", "body", "avatar",
|
||||||
|
"categories", "privacy", "emoticons", "comments"]:
|
||||||
|
g.info[field] = post[field]
|
||||||
|
|
||||||
|
# Dissect the time.
|
||||||
|
date = datetime.datetime.fromtimestamp(post["time"])
|
||||||
|
g.info.update(dict(
|
||||||
|
month="{:02d}".format(date.month),
|
||||||
|
day="{:02d}".format(date.day),
|
||||||
|
year=date.year,
|
||||||
|
hour="{:02d}".format(date.hour),
|
||||||
|
min="{:02d}".format(date.minute),
|
||||||
|
sec="{:02d}".format(date.second),
|
||||||
|
))
|
||||||
|
|
||||||
|
# Are we SUBMITTING the form?
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.form.get("action")
|
||||||
|
|
||||||
|
# Get all the fields from the posted params.
|
||||||
|
g.info["post_id"] = request.form.get("id")
|
||||||
|
for field in ["fid", "subject", "body", "avatar", "categories", "privacy"]:
|
||||||
|
g.info[field] = request.form.get(field)
|
||||||
|
for boolean in ["emoticons", "comments"]:
|
||||||
|
print "BOOL:", boolean, request.form.get(boolean)
|
||||||
|
g.info[boolean] = True if request.form.get(boolean, None) == "true" else False
|
||||||
|
print g.info[boolean]
|
||||||
|
for number in ["author", "month", "day", "year", "hour", "min", "sec"]:
|
||||||
|
g.info[number] = int(request.form.get(number, 0))
|
||||||
|
|
||||||
|
# What action are they doing?
|
||||||
|
if action == "preview":
|
||||||
|
g.info["preview"] = True
|
||||||
|
elif action == "publish":
|
||||||
|
# Publishing! Validate inputs first.
|
||||||
|
invalid = False
|
||||||
|
if len(g.info["body"]) == 0:
|
||||||
|
invalid = True
|
||||||
|
flash("You must enter a body for your blog post.")
|
||||||
|
if len(g.info["subject"]) == 0:
|
||||||
|
invalid = True
|
||||||
|
flash("You must enter a subject for your blog post.")
|
||||||
|
|
||||||
|
# Make sure the times are valid.
|
||||||
|
date = None
|
||||||
|
try:
|
||||||
|
date = datetime.datetime(
|
||||||
|
g.info["year"],
|
||||||
|
g.info["month"],
|
||||||
|
g.info["day"],
|
||||||
|
g.info["hour"],
|
||||||
|
g.info["min"],
|
||||||
|
g.info["sec"],
|
||||||
|
)
|
||||||
|
except ValueError, e:
|
||||||
|
invalid = True
|
||||||
|
flash("Invalid date/time: " + str(e))
|
||||||
|
|
||||||
|
# Format the categories.
|
||||||
|
tags = []
|
||||||
|
for tag in g.info["categories"].split(","):
|
||||||
|
tags.append(tag.strip())
|
||||||
|
|
||||||
|
# Okay to update?
|
||||||
|
if invalid is False:
|
||||||
|
# Convert the date into a Unix time stamp.
|
||||||
|
epoch = float(date.strftime("%s"))
|
||||||
|
|
||||||
|
new_id, new_fid = Blog.post_entry(
|
||||||
|
post_id = g.info["post_id"],
|
||||||
|
epoch = epoch,
|
||||||
|
author = g.info["author"],
|
||||||
|
subject = g.info["subject"],
|
||||||
|
fid = g.info["fid"],
|
||||||
|
avatar = g.info["avatar"],
|
||||||
|
categories = tags,
|
||||||
|
privacy = g.info["privacy"],
|
||||||
|
ip = request.remote_addr,
|
||||||
|
emoticons = g.info["emoticons"],
|
||||||
|
comments = g.info["comments"],
|
||||||
|
body = g.info["body"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(url_for(".entry", fid=new_fid))
|
||||||
|
|
||||||
|
|
||||||
|
if type(g.info["categories"]) is list:
|
||||||
|
g.info["categories"] = ", ".join(g.info["categories"])
|
||||||
|
|
||||||
|
return template("blog/update.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/delete", methods=["GET", "POST"])
|
||||||
|
def delete():
|
||||||
|
"""Delete a blog post."""
|
||||||
|
post_id = request.args.get("id")
|
||||||
|
|
||||||
|
# Resolve the post ID.
|
||||||
|
post_id = Blog.resolve_id(post_id)
|
||||||
|
if not post_id:
|
||||||
|
flash("That blog post wasn't found.")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
confirm = request.form.get("confirm")
|
||||||
|
if confirm == "true":
|
||||||
|
Blog.delete_entry(post_id)
|
||||||
|
flash("The blog entry has been deleted.")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
# Get the entry's subject.
|
||||||
|
post = Blog.get_entry(post_id)
|
||||||
|
g.info["subject"] = post["subject"]
|
||||||
|
g.info["post_id"] = post_id
|
||||||
|
|
||||||
|
return template("blog/delete.html")
|
||||||
|
|
||||||
|
def partial_index():
|
||||||
|
"""Partial template for including the index view of the blog."""
|
||||||
|
|
||||||
|
# Get the blog index.
|
||||||
|
index = Blog.get_index()
|
||||||
|
pool = {} # The set of blog posts to show.
|
||||||
|
|
||||||
|
category = g.info.get("url_category", None)
|
||||||
|
|
||||||
|
# Are we narrowing by category?
|
||||||
|
if category:
|
||||||
|
# Narrow down the index to just those that match the category.
|
||||||
|
for post_id, data in index.iteritems():
|
||||||
|
if not category in data["categories"]:
|
||||||
|
continue
|
||||||
|
pool[post_id] = data
|
||||||
|
|
||||||
|
# No such category?
|
||||||
|
if len(pool) == 0:
|
||||||
|
flash("There are no posts with that category.")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
else:
|
||||||
|
pool = index
|
||||||
|
|
||||||
|
# Separate the sticky posts from the normal ones.
|
||||||
|
sticky, normal = set(), set()
|
||||||
|
for post_id, data in pool.iteritems():
|
||||||
|
if data["sticky"]:
|
||||||
|
sticky.add(post_id)
|
||||||
|
else:
|
||||||
|
normal.add(post_id)
|
||||||
|
|
||||||
|
# Sort the blog IDs by published time.
|
||||||
|
posts = []
|
||||||
|
posts.extend(sorted(sticky, key=lambda x: pool[x]["time"], reverse=True))
|
||||||
|
posts.extend(sorted(normal, key=lambda x: pool[x]["time"], reverse=True))
|
||||||
|
|
||||||
|
# Handle pagination.
|
||||||
|
offset = request.args.get("skip", 0)
|
||||||
|
try: offset = int(offset)
|
||||||
|
except: offset = 0
|
||||||
|
|
||||||
|
# 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
|
||||||
|
if g.info["earlier"] < 0:
|
||||||
|
g.info["earlier"] = 0
|
||||||
|
if g.info["older"] < 0 or g.info["older"] > len(posts):
|
||||||
|
g.info["older"] = 0
|
||||||
|
g.info["count"] = 0
|
||||||
|
|
||||||
|
# Can we go to other pages?
|
||||||
|
g.info["can_earlier"] = True if offset > 0 else False
|
||||||
|
g.info["can_older"] = False if g.info["older"] == 0 else True
|
||||||
|
|
||||||
|
# Load the selected posts.
|
||||||
|
selected = []
|
||||||
|
stop = offset + BLOG_ENTRIES_PER_PAGE
|
||||||
|
if stop > len(posts): stop = len(posts)
|
||||||
|
for i in range(offset, stop):
|
||||||
|
post_id = posts[i]
|
||||||
|
post = Blog.get_entry(post_id)
|
||||||
|
|
||||||
|
post["post_id"] = post_id
|
||||||
|
|
||||||
|
# Get the author's information.
|
||||||
|
post["profile"] = User.get_user(uid=post["author"])
|
||||||
|
|
||||||
|
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
||||||
|
|
||||||
|
# TODO: count the comments for this post
|
||||||
|
post["comment_count"] = 0
|
||||||
|
|
||||||
|
selected.append(post)
|
||||||
|
g.info["count"] += 1
|
||||||
|
|
||||||
|
g.info["category"] = category
|
||||||
|
g.info["posts"] = selected
|
||||||
|
|
||||||
|
return template("blog/index.inc.html")
|
|
@ -2,21 +2,31 @@
|
||||||
|
|
||||||
# Legacy endpoint compatibility from kirsle.net.
|
# Legacy endpoint compatibility from kirsle.net.
|
||||||
|
|
||||||
from flask import request, redirect
|
from flask import request, redirect, url_for
|
||||||
from rophako import app
|
from rophako import app
|
||||||
|
import rophako.model.blog as Blog
|
||||||
|
|
||||||
@app.route("/+")
|
@app.route("/+")
|
||||||
def google_plus():
|
def google_plus():
|
||||||
return redirect("https://plus.google.com/+NoahPetherbridge/posts")
|
return redirect("https://plus.google.com/+NoahPetherbridge/posts")
|
||||||
|
|
||||||
@app.route("/blog.html")
|
@app.route("/blog.html")
|
||||||
def legacy_blog():
|
def ancient_legacy_blog():
|
||||||
post_id = request.args.get("id", "")
|
post_id = request.args.get("id", None)
|
||||||
|
if post_id is None:
|
||||||
|
return redirect(url_for("blog.index"))
|
||||||
|
|
||||||
# All of this is TO-DO.
|
# Look up the friendly ID.
|
||||||
# friendly_id = get friendly ID
|
post = Blog.get_entry(post_id)
|
||||||
# return redirect(...)
|
if not post:
|
||||||
return "TO-DO"
|
flash("That blog entry wasn't found.")
|
||||||
|
return redirect(url_for("blog.index"))
|
||||||
|
|
||||||
|
return redirect(url_for("blog.entry", fid=post["fid"]))
|
||||||
|
|
||||||
|
@app.route("/blog/kirsle/<fid>")
|
||||||
|
def legacy_blog(fid):
|
||||||
|
return redirect(url_for("blog.entry", fid=fid))
|
||||||
|
|
||||||
@app.route("/<page>.html")
|
@app.route("/<page>.html")
|
||||||
def legacy_url(page):
|
def legacy_url(page):
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from flask import g, session, request, render_template
|
from flask import g, session, request, render_template, flash, redirect, url_for
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import uuid
|
import uuid
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import importlib
|
||||||
|
|
||||||
from rophako.log import logger
|
from rophako.log import logger
|
||||||
|
|
||||||
|
@ -41,6 +45,10 @@ def template(name, **kwargs):
|
||||||
"""Render a template to the browser."""
|
"""Render a template to the browser."""
|
||||||
|
|
||||||
html = render_template(name, **kwargs)
|
html = render_template(name, **kwargs)
|
||||||
|
|
||||||
|
# Get the elapsed time for the request.
|
||||||
|
time_elapsed = "%.03f" % (time.time() - g.info["time"])
|
||||||
|
html = re.sub(r'\%time_elapsed\%', time_elapsed, html)
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,4 +56,23 @@ def generate_csrf_token():
|
||||||
"""Generator for CSRF tokens."""
|
"""Generator for CSRF tokens."""
|
||||||
if "_csrf" not in session:
|
if "_csrf" not in session:
|
||||||
session["_csrf"] = str(uuid.uuid4())
|
session["_csrf"] = str(uuid.uuid4())
|
||||||
return session["_csrf"]
|
return session["_csrf"]
|
||||||
|
|
||||||
|
|
||||||
|
def include(endpoint, *args, **kwargs):
|
||||||
|
"""Include another sub-page inside a template."""
|
||||||
|
|
||||||
|
# The 'endpoint' should be in the format 'module.function', i.e. 'blog.index'.
|
||||||
|
module, function = endpoint.split(".")
|
||||||
|
|
||||||
|
# Dynamically import the module and call its function.
|
||||||
|
m = importlib.import_module("rophako.modules.{}".format(module))
|
||||||
|
html = getattr(m, function)(*args, **kwargs)
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_time(time_format, unix):
|
||||||
|
"""Pretty-print a time stamp."""
|
||||||
|
date = datetime.datetime.fromtimestamp(unix)
|
||||||
|
return date.strftime(time_format)
|
17
rophako/www/blog/delete.html
Normal file
17
rophako/www/blog/delete.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Delete Entry{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Delete Entry</h1>
|
||||||
|
|
||||||
|
<form name="editor" action="{{ url_for('blog.delete', id=post_id) }}" method="POST">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="confirm" value="true">
|
||||||
|
|
||||||
|
Are you sure you want to delete the blog post,
|
||||||
|
"{{ subject }}"?<p>
|
||||||
|
|
||||||
|
<button type="submit">Confirm Deletion</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
10
rophako/www/blog/entry.html
Normal file
10
rophako/www/blog/entry.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ post["subject"] }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ post["subject"] }}</h1>
|
||||||
|
|
||||||
|
{% from "blog/entry.inc.html" import blog_entry %}
|
||||||
|
{{ blog_entry(post) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
69
rophako/www/blog/entry.inc.html
Normal file
69
rophako/www/blog/entry.inc.html
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{# Reusable template for showing a blog post content #}
|
||||||
|
|
||||||
|
{% macro blog_entry(post, from=None) %}
|
||||||
|
|
||||||
|
{% if from == "index" %}
|
||||||
|
<a href="{{ url_for('blog.entry', fid=post['fid']) }}" class="blog-title-index">
|
||||||
|
{{ post["subject"] }}
|
||||||
|
</a><p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="blog-author">
|
||||||
|
<a href="#">{# TODO #}
|
||||||
|
{% if post["avatar"] %}
|
||||||
|
<img src="/static/avatars/{{ post['avatar'] }}">
|
||||||
|
{% else %}
|
||||||
|
<img src="/static/avatars/default.png">
|
||||||
|
{% endif %}
|
||||||
|
</a><br>
|
||||||
|
|
||||||
|
<a href="#">
|
||||||
|
{{ post["profile"]["username"] }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blog-timestamp">
|
||||||
|
Posted by <a href="#">{{ post["profile"]["name"] }}</a>
|
||||||
|
on <span title="{{ post['time'] }}">{{ post["pretty_time"] }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ post["body"] | safe }}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<div class="clear">
|
||||||
|
<strong>Categories:</strong>
|
||||||
|
{% if post["categories"]|length == 0 %}
|
||||||
|
<a href="{{ url_for('blog.category', category=Uncategorized) }}">Uncategorized</a>{# TODO hardcoded name #}
|
||||||
|
{% else %}
|
||||||
|
<ul class="blog-categories">
|
||||||
|
{% for tag in post["categories"] %}
|
||||||
|
<li><a href="{{ url_for('blog.category', category=tag) }}">{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
|
||||||
|
[
|
||||||
|
{% if from == "index" %}
|
||||||
|
{% if post["comments"] %}{# Allowed comments #}
|
||||||
|
<a href="{{ url_for('blog.entry', fid=post['fid']) }}#comments">{{ post["comment_count"] }} comment{% if post["comment_count"] != 1 %}s{% endif %}</a>
|
||||||
|
|
|
||||||
|
<a href="{{ url_for('blog.entry', fid=post['fid']) }}#addcomment">Add comment</a>
|
||||||
|
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{{ url_for('blog.entry', fid=post['fid']) }}">Permalink</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('blog.index') }}">Blog</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if session["login"] %}
|
||||||
|
|
|
||||||
|
<a href="{{ url_for('blog.update', id=post['post_id']) }}">Edit</a>
|
||||||
|
|
|
||||||
|
<a href="{{ url_for('blog.delete', id=post['post_id']) }}">Delete</a>
|
||||||
|
{% endif %}
|
||||||
|
]
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endmacro %}
|
13
rophako/www/blog/index.html
Normal file
13
rophako/www/blog/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Blog{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if url_category %}
|
||||||
|
<h1>Category: {{ url_category }}</h1>
|
||||||
|
{% else %}
|
||||||
|
<h1>My Blog</h1>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ include_page("blog.partial_index") | safe }}
|
||||||
|
|
||||||
|
{% endblock %}
|
13
rophako/www/blog/index.inc.html
Normal file
13
rophako/www/blog/index.inc.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% from "blog/entry.inc.html" import blog_entry %}
|
||||||
|
|
||||||
|
{% include "blog/nav-links.inc.html" %}
|
||||||
|
|
||||||
|
{% if count == 0 %}
|
||||||
|
There are no blog posts yet.
|
||||||
|
{% else %}
|
||||||
|
{% for post in posts %}
|
||||||
|
{{ blog_entry(post, from="index") }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include "blog/nav-links.inc.html" %}
|
27
rophako/www/blog/nav-links.inc.html
Normal file
27
rophako/www/blog/nav-links.inc.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{# Older/Newer links #}
|
||||||
|
|
||||||
|
{% if can_older or can_newer %}
|
||||||
|
<div class="right">
|
||||||
|
[
|
||||||
|
<a href="/rss">RSS Feed</a> | {# TODO! #}
|
||||||
|
{% if can_earlier %}
|
||||||
|
{% if category %}
|
||||||
|
<a href="{{ url_for('blog.category', category=category) }}?skip={{ earlier }}">< Newer</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('blog.index') }}?skip={{ earlier }}">< Newer</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if can_older %} | {% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if can_older %}
|
||||||
|
{% if category %}
|
||||||
|
<a href="{{ url_for('blog.category', category=category) }}?skip={{ older }}">Older ></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('blog.index') }}?skip={{ older }}">Older ></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
]
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
152
rophako/www/blog/update.html
Normal file
152
rophako/www/blog/update.html
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Update Blog{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if preview %}
|
||||||
|
<h1>Preview: {{ subject }}</h1>
|
||||||
|
|
||||||
|
{{ body|safe }}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h1>Update Blog</h1>
|
||||||
|
|
||||||
|
<form name="editor" action="{{ url_for('blog.update') }}" method="POST">
|
||||||
|
<input type="hidden" name="id" value="{{ post_id }}">
|
||||||
|
<input type="hidden" name="author" value="{{ author }}">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<strong>Subject:</strong><br>
|
||||||
|
<input type="text" size="80" name="subject" value="{{ subject }}"><p>
|
||||||
|
|
||||||
|
<strong>Friendly ID:</strong><br>
|
||||||
|
You can leave this blank if this is a new post. It defaults to be based
|
||||||
|
on the subject.<br>
|
||||||
|
<input type="text" size="80" name="fid" value="{{ fid }}"><p>
|
||||||
|
|
||||||
|
<strong>Body:</strong><br>
|
||||||
|
<textarea cols="80" rows="12" name="body">{{ body }}</textarea><br>
|
||||||
|
<a href="/emoticons" target="_blank">Emoticon reference</a> (opens in new window)<p>
|
||||||
|
|
||||||
|
<strong>Avatar:</strong><br>
|
||||||
|
<span id="avatar-preview"></span>
|
||||||
|
<select name="avatar" id="avatar">
|
||||||
|
<option value=""{% if avatar == "" %} selected{% endif %}>Use my profile picture</option>
|
||||||
|
{% for pic in avatars %}
|
||||||
|
<option value="{{ pic }}"{% if avatar == pic %} selected{% endif %}>{{ pic }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select><p>
|
||||||
|
|
||||||
|
<strong>Categories:</strong><br>
|
||||||
|
<small>Comma-separated list, e.g. General, HTML, Perl, Web Design</small><br>
|
||||||
|
<input type="text" size="40" name="categories" value="{{ categories }}"><p>
|
||||||
|
|
||||||
|
<strong>Privacy:</strong><br>
|
||||||
|
<select name="privacy">
|
||||||
|
<option value="public"{% if privacy == "public" %} selected{% endif %}>
|
||||||
|
Public: everybody can see this blog entry
|
||||||
|
</option>
|
||||||
|
<option value="private"{% if privacy == "private" %} selected{% endif %}>
|
||||||
|
Private: only site admins can see this blog entry
|
||||||
|
</option>
|
||||||
|
</select><p>
|
||||||
|
|
||||||
|
<strong>Options:</strong><br>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="emoticons" value="true"{% if emoticons %} checked{% endif %}>
|
||||||
|
Enable graphical emoticons
|
||||||
|
</label><br>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="comments" value="true"{% if comments %} checked{% endif %}>
|
||||||
|
Enable comments on this entry
|
||||||
|
</label><p>
|
||||||
|
|
||||||
|
<strong>Time Stamp:</strong><br>
|
||||||
|
<input type="text" size="2" name="month" id="month" value="{{ month }}"> /
|
||||||
|
<input type="text" size="2" name="day" id="day" value="{{ day }}"> /
|
||||||
|
<input type="text" size="4" name="year" id="year" value="{{ year }}"> @
|
||||||
|
<input type="text" size="2" name="hour" id="hour" value="{{ hour }}"> :
|
||||||
|
<input type="text" size="2" name="min" id="min" value="{{ min }}"> :
|
||||||
|
<input type="text" size="2" name="sec" id="sec" value="{{ sec }}"><br>
|
||||||
|
mm / dd / yyyy @ hh:mm:ss<br>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="autoup" value="yes"{% if post_id == "" %} checked{% endif %}>
|
||||||
|
Automatically update
|
||||||
|
</label><p>
|
||||||
|
|
||||||
|
<button type="submit" name="action" value="preview">Preview</button>
|
||||||
|
<button type="submit" name="action" value="publish">Publish Entry</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
var userPic = "/static/avatars/default.png"; // TODO
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Preview their selected avatar.
|
||||||
|
updateAvatar();
|
||||||
|
$("#avatar").on("change", updateAvatar);
|
||||||
|
|
||||||
|
// Start ticking the timestamp updater.
|
||||||
|
setInterval(timestamps, 500)
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateAvatar() {
|
||||||
|
var chosen = $("#avatar").val();
|
||||||
|
|
||||||
|
var picture = ""; // which pic to show
|
||||||
|
if (chosen === "") {
|
||||||
|
picture = userPic;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
picture = "/static/avatars/" + chosen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the pic
|
||||||
|
if (picture.length) {
|
||||||
|
$("#avatar-preview").html("<img src=\"" + picture + "\" alt=\"Preview\"><br>");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#avatar-preview").html("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function timestamps() {
|
||||||
|
function padout(num) {
|
||||||
|
if (num < 10) {
|
||||||
|
return '0' + num;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($("#autoup").is(":checked")) {
|
||||||
|
var d = new Date();
|
||||||
|
var mon = d.getMonth(); // 0..11
|
||||||
|
var day = d.getDate(); // 1..31
|
||||||
|
var year = d.getFullYear(); // 2014
|
||||||
|
var hour = d.getHours(); // 0..23
|
||||||
|
var min = d.getMinutes(); // 0..59
|
||||||
|
var sec = d.getSeconds(); // 0..59
|
||||||
|
|
||||||
|
// Adjust the dates.
|
||||||
|
mon++;
|
||||||
|
mon = padout(mon);
|
||||||
|
day = padout(day);
|
||||||
|
hour = padout(hour);
|
||||||
|
min = padout(min);
|
||||||
|
sec = padout(sec);
|
||||||
|
|
||||||
|
// Update the fields.
|
||||||
|
$("#month").val(mon);
|
||||||
|
$("#day").val(day);
|
||||||
|
$("#year").val(year);
|
||||||
|
$("#hour").val(hour);
|
||||||
|
$("#min").val(min);
|
||||||
|
$("#sec").val(sec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
<h1>Welcome!</h1>
|
<h1>Welcome!</h1>
|
||||||
|
|
||||||
This is the Rophako CMS!
|
This is the Rophako CMS!<p>
|
||||||
|
|
||||||
|
{{ include_page("blog.partial_index") | safe }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
BIN
rophako/www/static/avatars/default.png
Normal file
BIN
rophako/www/static/avatars/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
88
scripts/siikir-blog-migrate.py
Normal file
88
scripts/siikir-blog-migrate.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""Migrate blog database files from a PerlSiikir site to Rophako.
|
||||||
|
|
||||||
|
Usage: scripts/siikir-blog-migrate.py <path/to/siikir/db/root>
|
||||||
|
|
||||||
|
Rophako supports one global blog, so the blog of UserID 1 in Siikir is used."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import json
|
||||||
|
import glob
|
||||||
|
|
||||||
|
sys.path.append(".")
|
||||||
|
import rophako.jsondb as JsonDB
|
||||||
|
|
||||||
|
# Path to Siikir DB root.
|
||||||
|
siikir = None
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
print "Usage: {} <path/to/siikir/db/root>".format(__file__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
global siikir
|
||||||
|
siikir = sys.argv[1]
|
||||||
|
print "Siikir DB:", siikir
|
||||||
|
if raw_input("Confirm? [yN] ") != "y":
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
convert_index()
|
||||||
|
#convert_tags()
|
||||||
|
convert_posts()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_index():
|
||||||
|
print "Converting blog index"
|
||||||
|
index = json_get("blog/index/1.json")
|
||||||
|
new = {}
|
||||||
|
for post_id, data in index.iteritems():
|
||||||
|
del data["id"]
|
||||||
|
|
||||||
|
# Enforce data types.
|
||||||
|
data["author"] = int(data["author"])
|
||||||
|
data["time"] = int(data["time"])
|
||||||
|
data["sticky"] = bool(data["sticky"])
|
||||||
|
|
||||||
|
new[post_id] = data
|
||||||
|
|
||||||
|
JsonDB.commit("blog/index", new)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_tags():
|
||||||
|
print "Converting tag index"
|
||||||
|
index = json_get("blog/tags/1.json")
|
||||||
|
JsonDB.commit("blog/tags", index)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_posts():
|
||||||
|
print "Converting blog entries..."
|
||||||
|
|
||||||
|
for name in glob.glob(os.path.join(siikir, "blog/entries/1/*.json")):
|
||||||
|
name = name.split("/")[-1]
|
||||||
|
post = json_get("blog/entries/1/{}".format(name))
|
||||||
|
post_id = post["id"]
|
||||||
|
del post["id"]
|
||||||
|
|
||||||
|
# Enforce data types.
|
||||||
|
post["time"] = int(post["time"])
|
||||||
|
post["author"] = int(post["author"])
|
||||||
|
post["comments"] = bool(post["comments"])
|
||||||
|
post["sticky"] = bool(post["sticky"])
|
||||||
|
post["emoticons"] = bool(post["emoticons"])
|
||||||
|
|
||||||
|
print "*", post["subject"]
|
||||||
|
JsonDB.commit("blog/entries/{}".format(post_id), post)
|
||||||
|
|
||||||
|
|
||||||
|
def json_get(document):
|
||||||
|
fh = codecs.open(os.path.join(siikir, document), 'r', 'utf-8')
|
||||||
|
text = fh.read()
|
||||||
|
fh.close()
|
||||||
|
return json.loads(text)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user