Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
327 lines (263 sloc) 10.4 KB
# Copyright 2014 Google Inc. All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
from datetime import datetime
import os.path
from operator import attrgetter
from random import choice
from sqlalchemy.orm.exc import NoResultFound
from flask import abort, flash, redirect, render_template, request, url_for
from flask.ext.holster.main import init_holster
from flask.ext.login import current_user
from import DCoN
from newrem.converters import make_model_converter
from newrem.decorators import cached
from newrem.files import assets_in_paths, save_file
from newrem.filters import url_for_comic
from newrem.forms import CommentForm
from newrem.grammars import BlogGrammar
from newrem.models import (db, Board, Character, Comic, Newspost, Post,
from newrem.util import chan_filename, make_rss2
app = DCoN(__name__)
# Register converters.
app.url_map.converters["board"] = make_model_converter(app, Board,
app.url_map.converters["character"] = make_model_converter(app, Character,
app.url_map.converters["newspost"] = make_model_converter(app, Newspost,
app.url_map.converters["universe"] = make_model_converter(app, Universe,
def blogify(s):
Run a string through a grammar to prettify it somewhat.
return BlogGrammar(s).paragraphs()
def eblogify(s):
Like ``blogify``, but also apply HTML escapes. For untrusted input.
return BlogGrammar(s).safe_paragraphs()
def get_comic_query(universe):
Make a comic query.
This helper keeps the temporal filter on all comic queries not otherwise
safe. The point of the filter is to prevent comics which are posted with a
timestamp in the future from being displayed or otherwise referenced; as
far as anybody can tell, those comics simply do not exist until after the
timestamp elapses. The filter also prevents comics in other universes from
being selected.
q = Comic.query.filter(Comic.universe == universe)
return q.filter(Comic.time <
def get_neighbors_for(universe, comic):
Grab the comics around a given comic.
comics = {}
# Grab the comics corresponding to navigation buttons: First, previous,
# next, last. This first query doesn't need to have the temporal filter.
q = Comic.query.filter(Comic.universe == universe)
q = q.filter(Comic.time < comic.time)
a = q.order_by(Comic.time).first()
b = q.order_by(Comic.time.desc()).first()
q = get_comic_query(universe).filter(Comic.time > comic.time)
c = q.order_by(Comic.time).first()
d = q.order_by(Comic.time.desc()).first()
comics["upload"] = a, b, c, d
return comics
def index():
universes = Universe.query.all()
newsposts = Newspost.query.order_by(Newspost.time.desc())[:5]
return render_template("index.html", universes=universes,
def universe_context(app, u):
segments = [u.slug, "images", "banners"]
banners = assets_in_paths(app, segments)
if banners:
banner = "/".join(segments)
banner = None
return {
"u": u,
"banner": banner,
def cast(u):
# Re-add the universe to the session so that we can query it.
q = Character.query.filter_by(universe=u, major=True)
characters = sorted(q.all(), key=attrgetter("name"))
context = universe_context(app, u)
"characters": characters,
return render_template("universe/cast.html", **context)
def recent(u):
char = request.args.get("char", None)
if char is not None:
if char == "anything-goes":
# Get all characters that exist in this universe who have had any
# appearances. This could fail, in which case we bail with a cute
# error message.
q = Character.query.filter_by(universe=u)
chars = q.filter(Character.comics.any()).all()
if not chars:
flash("No characters have been introduced yet. Patience, "
return redirect(url_for("recent", u=u))
char = choice(chars)
char = Character.query.filter_by(universe=u, slug=char).first()
if not char:
flash("That character doesn't exist. I'm sorry, but we don't "
"have fanfiction-based characters in the story.")
return redirect(url_for("recent", u=u))
elif not char.comics:
flash("That character has not made an appearance yet. We "
"know how popular they are, though, so be prepared for "
"their debut!")
return redirect(url_for("recent", u=u))
q = get_comic_query(u)
if char:
q = q.filter(Comic.characters.contains(char))
comic = q.order_by(
if not comic:
# We probably shouldn't have been able to get here, in general. This
# could happen if there are no comics in this universe yet. However,
# it could possibly happen if there is some bug in the above
# character-sorting logic. Be aware of this when debugging this code
# later. :3
flash("No comics could be found. Something went wrong, perhaps?")
return redirect(url_for("index"))
if char:
return redirect(url_for_comic(comic, char=char.slug))
return redirect(url_for_comic(comic))
def comics(u, cid, name=None):
# The name is purely decorative.
comic = get_comic_query(u).filter_by(id=cid).one()
except NoResultFound:
char = request.args.get("char", None)
if char is not None:
char = Character.query.filter_by(universe=u, slug=char).first()
if char is None:
flash("That character doesn't exist, and typing them into the "
"URL doesn't magically spring them into the comic. Sorry.")
elif char not in comic.characters:
flash("That character isn't in this particular comic, and won't "
"be, no matter how hard you wish. Sorry.")
char = None
comics = get_neighbors_for(u, comic)
before = get_comic_query(u).filter(Comic.position < comic.position)
# And reverse it for first() and such.
before = before.order_by(Comic.position.desc())
after = get_comic_query(u).filter(Comic.position > comic.position)
after = after.order_by(Comic.position)
chrono = before.first(), after.first()
cdict = {}
majors = Character.query.filter_by(universe=u, major=True).all()
universes = Universe.query.all()
for character in comic.characters:
pred = Comic.characters.any(Character.slug == character.slug)
previous = before.filter(pred).first()
next = after.filter(pred).first()
# SQLAlchemy doesn't make this any easier.
first = get_comic_query(u).filter(pred).order_by(Comic.position).first()
last = get_comic_query(u).filter(pred).order_by(Comic.position.desc()).first()
cdict[character.slug] = character, first, previous, next, last
# Buffer watch feature. Figure out the datetime for the latest comic
# uploaded, and send that datetime along.
q = Comic.query.filter(Comic.universe == u)
buffered = q.order_by(Comic.time.desc()).first().time
context = universe_context(app, u)
"buffered": buffered,
"comic": comic,
"comics": comics,
"chrono": chrono,
"characters": cdict,
"majors": majors,
"universes": universes,
return render_template("universe/comics.html", **context)
@app.route("/<universe:u>/comics/<int:cid>/comment", methods=("POST",))
def comment(u, cid):
if current_user.is_anonymous():
comic = get_comic_query(u).filter_by(id=cid).one()
except NoResultFound:
form = CommentForm()
if form.validate_on_submit():
name = "Anonymous"
name = current_user.username
post = Post(name,, "", None)
post.thread = comic.thread
image = form.datafile.file
if image:
post.filename = chan_filename(image)
save_file(post.fp(), image)
return redirect(url_for_comic(comic))
def rss():
# Filter out comics that have not yet gone live.
q = Comic.query.filter(Comic.time <
comics = q.order_by(Comic.time.desc())[:10]
stuff = []
for comic in comics:
url = url_for_comic(comic, _external=True)
stuff.append((url, comic))
link = url_for("index", _external=True)
return make_rss2(link, "DCoN", stuff)
def universe_rss(u):
q = get_comic_query(u).order_by(Comic.time.desc())
comics = q[:10]
stuff = []
for comic in comics:
url = url_for_comic(comic, _external=True)
stuff.append((url, comic))
link = url_for("recent", _external=True, u=u)
return make_rss2(link, u.title, stuff)
def not_found(error):
segments = ["404"]
images = assets_in_paths(app, segments)
filename = choice(images)
image = os.path.join("404", filename)
# 404s should still send the 404 status to the application.
return render_template("404.html", image=image), 404, {}
def internal_server_error(error):
# Do as little work as possible; just pass along that a 500 happened.
return render_template("500.html"), 500, {}