Add RSS feed, contact page, Kirsle.net stuff
This commit is contained in:
parent
4bb9b5d687
commit
9a4f74844d
|
@ -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():
|
||||
|
|
67
rophako/modules/contact.py
Normal file
67
rophako/modules/contact.py
Normal file
|
@ -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)
|
19
rophako/www/blog/categories.inc.html
Normal file
19
rophako/www/blog/categories.inc.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% for tag in tags %}
|
||||
{% if not tag["small"] %}
|
||||
» <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"] %}
|
||||
» <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">
|
||||
¤ <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 }}">< Newer</a>
|
||||
|
|
48
rophako/www/contact/index.html
Normal file
48
rophako/www/contact/index.html
Normal file
|
@ -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 %}
|
Loading…
Reference in New Issue
Block a user