Add RSS feed, contact page, Kirsle.net stuff

pull/2/head
Noah 2014-04-06 18:02:34 -07:00
コミット 9a4f74844d
10個のファイルの変更350行の追加34行の削除

ファイルの表示

@ -4,6 +4,7 @@
import os
_basedir = os.path.abspath(os.path.dirname(__file__))
import datetime
DEBUG = True
@ -63,6 +64,19 @@ 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
# RSS feed settings.
RSS_TITLE = "Kirsle.net"
RSS_LINK = "http://www.kirsle.net/"
RSS_LANGUAGE = "en"
RSS_DESCRIPTION = "The web blog of Kirsle"
RSS_COPYRIGHT = "Copyright {}, Kirsle.net".format(str(datetime.datetime.now().strftime("%Y")))
RSS_WEBMASTER = NOTIFY_ADDRESS[0]
RSS_IMAGE_TITLE = RSS_TITLE
RSS_IMAGE_URL = "http://www.kirsle.net/static/avatars/casey.png"
RSS_IMAGE_WIDTH = 96
RSS_IMAGE_HEIGHT = 96
RSS_IMAGE_DESCRIPTION = "Kirsle's Avatar"
################################################################################
## Photo Settings ##
################################################################################

ファイルの表示

@ -21,12 +21,14 @@ from rophako.modules.blog import mod as BlogModule
from rophako.modules.photo import mod as PhotoModule
from rophako.modules.comment import mod as CommentModule
from rophako.modules.emoticons import mod as EmoticonsModule
from rophako.modules.contact import mod as ContactModule
app.register_blueprint(AdminModule)
app.register_blueprint(AccountModule)
app.register_blueprint(BlogModule)
app.register_blueprint(PhotoModule)
app.register_blueprint(CommentModule)
app.register_blueprint(EmoticonsModule)
app.register_blueprint(ContactModule)
# Custom Jinja handler to support custom- and default-template folders for
# rendering templates.
@ -114,6 +116,8 @@ def catchall(path):
return send_file(abspath)
elif not "." in path and os.path.isfile(abspath + ".html"):
return rophako.utils.template(path + ".html")
elif not "." in path and os.path.isfile(abspath + "/index.html"):
return rophako.utils.template(path + "/index.html")
return not_found("404")
@ -125,7 +129,6 @@ def index():
@app.errorhandler(404)
def not_found(error):
print "NOT FOUND"
return render_template('errors/404.html', **g.info), 404

ファイルの表示

@ -46,26 +46,19 @@ def get_index():
return db
def __get_categories():
"""Get the blog categories cache.
def get_categories():
"""Get the blog categories and their popularity."""
index = get_index()
The category cache is in the following format:
# Group by tags.
tags = {}
for post, data in index.iteritems():
for tag in data["categories"]:
if not tag in tags:
tags[tag] = 0
tags[tag] += 1
```
{
'category_name': {
'post_id': 'friendly_id',
...
},
...
}
```
"""
# Index doesn't exist?
if not JsonDB.exists("blog/tags"):
return {}
return JsonDB.get("blog/tags")
return tags
def get_entry(post_id):

ファイルの表示

@ -6,6 +6,8 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash
import re
import datetime
import calendar
import time
from xml.dom.minidom import Document
import rophako.model.user as User
import rophako.model.blog as Blog
@ -225,6 +227,85 @@ def delete():
return template("blog/delete.html")
@mod.route("/rss")
def rss():
"""RSS feed for the blog."""
doc = Document()
rss = doc.createElement("rss")
rss.setAttribute("version", "2.0")
rss.setAttribute("xmlns:blogChannel", "http://backend.userland.com/blogChannelModule")
doc.appendChild(rss)
channel = doc.createElement("channel")
rss.appendChild(channel)
rss_time = "%a, %d %b %Y %H:%M:%S GMT"
######
## Channel Information
######
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],
["pubDate", today],
["lastBuildDate", today],
["webmaster", RSS_WEBMASTER],
])
######
## Image Information
######
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],
])
######
## Add the blog posts
######
index = Blog.get_index()
posts = get_index_posts(index)
for post_id in posts[:BLOG_ENTRIES_PER_RSS]:
post = Blog.get_entry(post_id)
item = doc.createElement("item")
channel.appendChild(item)
xml_add_text_tags(doc, item, [
["title", post["subject"]],
["link", url_for("blog.entry", fid=post["fid"])],
["description", post["body"]],
["pubDate", time.strftime(rss_time, time.gmtime(post["time"]))],
])
return doc.toprettyxml(encoding="utf-8")
def xml_add_text_tags(doc, root_node, tags):
"""RSS feed helper function.
Add a collection of simple tag/text pairs to a root XML element."""
for pair in tags:
name, value = pair
channelTag = doc.createElement(name)
channelTag.appendChild(doc.createTextNode(str(value)))
root_node.appendChild(channelTag)
def partial_index():
"""Partial template for including the index view of the blog."""
@ -249,18 +330,8 @@ def partial_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))
# Get the posts we want.
posts = get_index_posts(pool)
# Handle pagination.
offset = request.args.get("skip", 0)
@ -313,3 +384,41 @@ def partial_index():
g.info["posts"] = selected
return template("blog/index.inc.html")
def get_index_posts(index):
"""Helper function to get data for the blog index page."""
# Separate the sticky posts from the normal ones.
sticky, normal = set(), set()
for post_id, data in index.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: index[x]["time"], reverse=True))
posts.extend(sorted(normal, key=lambda x: index[x]["time"], reverse=True))
return posts
def partial_tags():
"""Get a listing of tags and their quantities for the nav bar."""
tags = Blog.get_categories()
# Sort the tags by popularity.
sort_tags = [ tag for tag in sorted(tags.keys(), key=lambda y: tags[y], reverse=True) ]
result = []
has_small = False
for tag in sort_tags:
result.append(dict(
category=tag,
count=tags[tag],
small=tags[tag] < 3, # TODO: make this configurable
))
if tags[tag] < 3:
has_small = True
g.info["tags"] = result
g.info["has_small"] = has_small
return template("blog/categories.inc.html")

ファイルの表示

@ -14,7 +14,6 @@ from config import *
mod = Blueprint("comment", __name__, url_prefix="/comments")
## TODO: emoticon support
@mod.route("/")
def index():

ファイルの表示

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
"""Endpoints for contacting the site owner."""
from flask import Blueprint, g, request, redirect, url_for, session, flash
import re
import time
from rophako.utils import template, send_email
from rophako.log import logger
from config import *
mod = Blueprint("contact", __name__, url_prefix="/contact")
@mod.route("/")
def index():
return template("contact/index.html")
@mod.route("/send", methods=["POST"])
def send():
"""Submitting the contact form."""
name = request.form.get("name", "") or "Anonymous"
email = request.form.get("email", "")
subject = request.form.get("subject", "") or "[No Subject]"
message = request.form.get("message", "")
# Spam traps.
trap1 = request.form.get("contact", "x") != ""
trap2 = request.form.get("website", "x") != "http://"
if trap1 or trap2:
flash("Wanna try that again?")
return redirect(url_for(".index"))
# Message is required.
if len(message) == 0:
flash("The message is required.")
return redirect(url_for(".index"))
# Send the e-mail.
send_email(
to=NOTIFY_ADDRESS,
subject="Contact Form on {}: {}".format(SITE_NAME, subject),
message="""A visitor to {site_name} has sent you a message!
IP Address: {ip}
User Agent: {ua}
Referrer: {referer}
Name: {name}
E-mail: {email}
Subject: {subject}
{message}""".format(
site_name=SITE_NAME,
ip=request.remote_addr,
ua=request.user_agent.string,
referer=request.headers.get("Referer", ""),
name=name,
email=email,
subject=subject,
message=message,
)
)
flash("Your message has been delivered.")
return redirect(url_for("index"))

ファイルの表示

@ -2,14 +2,21 @@
# Legacy endpoint compatibility from kirsle.net.
from flask import request, redirect, url_for
from flask import g, request, redirect, url_for, flash
import re
import os
from rophako import app
from rophako.utils import template
import rophako.model.blog as Blog
import rophako.jsondb as JsonDB
@app.route("/+")
def google_plus():
return redirect("https://plus.google.com/+NoahPetherbridge/posts")
@app.route("/blog.html")
def ancient_legacy_blog():
post_id = request.args.get("id", None)
@ -24,10 +31,67 @@ def ancient_legacy_blog():
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("/rss.cgi")
def legacy_rss():
return redirect(url_for("blog.rss"))
@app.route("/firered/<page>")
@app.route("/firered")
def legacy_firered(page=""):
g.info["page"] = str(page) or "1"
return template("firered.html")
@app.route("/download", methods=["GET", "POST"])
def legacy_download():
form = None
if request.method == "POST":
form = request.form
else:
form = request.args
method = form.get("method", "index")
project = form.get("project", "")
filename = form.get("file", "")
root = "/home/kirsle/www/projects"
if project and filename:
# Filter the sections.
project = re.sub(r'[^A-Za-z0-9]', '', project) # Project name is alphanumeric only.
filename = re.sub(r'[^A-Za-z0-9\-_\.]', '', filename)
# Check that all the files exist.
if os.path.isdir(os.path.join(root, project)) and os.path.isfile(os.path.join(root, project, filename)):
# Hit counters.
hits = { "hits": 0 }
db = "data/downloads/{}-{}".format(project, filename)
if JsonDB.exists(db.format(project, filename)):
hits = JsonDB.get(db)
# Actually getting the file?
if method == "get":
# Up the hit counter.
hits["hits"] += 1
JsonDB.commit(db, hits)
g.info["method"] = method
g.info["project"] = project
g.info["file"] = filename
g.info["hits"] = hits["hits"]
return template("download.html")
flash("The file or project wasn't found.")
return redirect(url_for("index"))
@app.route("/<page>.html")
def legacy_url(page):
return "/{}".format(page)

ファイルの表示

@ -0,0 +1,19 @@
{% for tag in tags %}
{% if not tag["small"] %}
&#0187; <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
<small>({{ tag['count'] }})</small><br>
{% endif %}
{% endfor %}
{% if has_small %}
<div id="blog_show_more" style="display: none">
{% for tag in tags %}
{% if tag["small"] %}
&#0187; <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
<small>({{ tag['count'] }})</small><br>
{% endif %}
{% endfor %}
</div>
<div id="blog_show_less" style="display: block">
&#0164; <a href="#" onClick="$('#blog_show_less').hide(); $('#blog_show_more').show(1000); return false">Show more...</a>
</div>
{% endif %}

ファイルの表示

@ -3,7 +3,7 @@
{% if can_older or can_newer %}
<div class="right">
[
<a href="/rss">RSS Feed</a> | {# TODO! #}
<a href="{{ url_for('blog.rss') }}">RSS Feed</a> |
{% if can_earlier %}
{% if category %}
<a href="{{ url_for('blog.category', category=category) }}?skip={{ earlier }}">&lt; Newer</a>

ファイルの表示

@ -0,0 +1,48 @@
{% extends "layout.html" %}
{% block title %}Contact Me{% endblock %}
{% block content %}
<h1>Contact Me</h1>
You can use the form below to send me an e-mail.<p>
<form name="contact" action="{{ url_for('contact.send') }}" method="POST">
<input type="hidden" name="token" value="{{ csrf_token() }}">
<table border="0" cellspacing="0" cellpadding="2">
<tr>
<td width="50%" align="left" valign="middle">
<strong>Your name:</strong><br>
<small>(so I know who you are)</small><br>
<input type="text" size="40" name="name">
</td>
<td width="50%" align="left" valign="middle">
<strong>Your email:</strong><br>
<small>(if you want a response)</small><br>
<input type="email" size="40" name="email">
</td>
</tr>
<tr>
<td colspan="2" align="left" valign="middle">
<strong>Message subject:</strong><br>
<small>(optional)</small><br>
<input type="text" size="40" name="subject" style="width: 100%"><p>
<strong>Message:</strong><br>
<small>(required)</small><br>
<textarea cols="40" rows="12" name="message" style="width: 100%"></textarea>
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="middle">
<button type="submit">Send Message</button>
</td>
</tr>
</table>
<div style="display: none">
If you can see these boxes, don't touch them.<br>
<input type="text" size="40" name="contact" value=""><br>
<input type="text" size="40" name="website" value="http://">
</div>
</form>
{% endblock %}