Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 297 lines (236 sloc) 9.495 kb
ff1e213 @MostAwesomeDude Add RSS.
authored
1 from datetime import datetime
a3d1196 @MostAwesomeDude Split main into a package.
authored
2 import os.path
34f992d @MostAwesomeDude Get cast wired up again, and attach Character to Universe.
authored
3 from operator import attrgetter
6a4aacb @MostAwesomeDude views, static: Add a real 404 handler.
authored
4 from random import choice
a3d1196 @MostAwesomeDude Split main into a package.
authored
5
6 from sqlalchemy.orm.exc import NoResultFound
7
68406ab @MostAwesomeDude views: Attempt to grab per-universe templates before the base templates.
authored
8 from jinja2.exceptions import TemplateNotFound
9
010c0ba @MostAwesomeDude views: Character filtering, part one.
authored
10 from flask import (Flask, abort, flash, redirect, render_template, request,
11 url_for)
11dc8a6 @MostAwesomeDude Implement comment form handling.
authored
12 from flaskext.login import current_user
a3d1196 @MostAwesomeDude Split main into a package.
authored
13
6f51a85 @MostAwesomeDude Get a universe view and converter working.
authored
14 from newrem.converters import make_model_converter
b624341 @MostAwesomeDude Start splitting the admin stuff to a blueprint.
authored
15 from newrem.decorators import cached
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
16 from newrem.forms import CommentForm
79aabfa @MostAwesomeDude Wire up a blogify filter, and use it on blog posts.
authored
17 from newrem.grammars import BlogGrammar
074c005 @MostAwesomeDude Update OC templates for board converters in views.
authored
18 from newrem.models import (db, Board, Character, Comic, Newspost, Post,
19 Universe)
d721f4f @MostAwesomeDude Switch over to an embedded OC.
authored
20 from newrem.util import chan_filename, make_rss2
a3d1196 @MostAwesomeDude Split main into a package.
authored
21
eaeb0ea @MostAwesomeDude Spawn app at views, start rebuilding views.
authored
22 app = Flask(__name__)
23
6f51a85 @MostAwesomeDude Get a universe view and converter working.
authored
24 # Register converters.
074c005 @MostAwesomeDude Update OC templates for board converters in views.
authored
25 app.url_map.converters["board"] = make_model_converter(app, Board,
26 "abbreviation")
3d7abf5 @MostAwesomeDude More admin stuff. Make characters work.
authored
27 app.url_map.converters["character"] = make_model_converter(app, Character,
28 "slug")
6925a23 @MostAwesomeDude Add the ability to edit the news.
authored
29 app.url_map.converters["newspost"] = make_model_converter(app, Newspost,
30 "title")
6f51a85 @MostAwesomeDude Get a universe view and converter working.
authored
31 app.url_map.converters["universe"] = make_model_converter(app, Universe,
32 "slug")
33
79aabfa @MostAwesomeDude Wire up a blogify filter, and use it on blog posts.
authored
34 @app.template_filter()
35 def blogify(s):
36 """
37 Run a string through a grammar to prettify it somewhat.
38 """
39
40 return BlogGrammar(s).apply("paragraphs")[0]
41
54f0948 @MostAwesomeDude views: Add eblogify filter.
authored
42 @app.template_filter()
43 def eblogify(s):
44 """
45 Like ``blogify``, but also apply HTML escapes. For untrusted input.
46 """
47
48 return BlogGrammar(s).apply("safe_paragraphs")[0]
49
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
50 def get_comic_query(universe):
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
51 """
52 Make a comic query.
53
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
54 This helper keeps the temporal filter on all comic queries not otherwise
55 safe. The point of the filter is to prevent comics which are posted with a
56 timestamp in the future from being displayed or otherwise referenced; as
57 far as anybody can tell, those comics simply do not exist until after the
58 timestamp elapses. The filter also prevents comics in other universes from
59 being selected.
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
60 """
61
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
62 q = Comic.query.filter(Comic.universe == universe)
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
63 return q.filter(Comic.time < datetime.now())
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
64
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
65 def get_neighbors_for(universe, comic):
c974418 @MostAwesomeDude Make navigation show up.
authored
66 """
67 Grab the comics around a given comic.
68 """
69
70 comics = {}
71
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
72 # Grab the comics corresponding to navigation buttons: First, previous,
73 # next, last. This first query doesn't need to have the temporal filter.
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
74 q = Comic.query.filter(Comic.universe == universe)
75 q = q.filter(Comic.time < comic.time)
f560b13 @MostAwesomeDude views: Fix order of comics.
authored
76 a = q.order_by(Comic.time).first()
77 b = q.order_by(Comic.time.desc()).first()
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
78
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
79 q = get_comic_query(universe).filter(Comic.time > comic.time)
c974418 @MostAwesomeDude Make navigation show up.
authored
80 c = q.order_by(Comic.time).first()
81 d = q.order_by(Comic.time.desc()).first()
82
83 comics["upload"] = a, b, c, d
84
85 return comics
86
a3d1196 @MostAwesomeDude Split main into a package.
authored
87 @app.route("/")
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
88 def index():
eaeb0ea @MostAwesomeDude Spawn app at views, start rebuilding views.
authored
89 universes = Universe.query.all()
f9a2d74 @MostAwesomeDude Get newsposts working again.
authored
90 newsposts = Newspost.query.order_by(Newspost.time.desc())[:5]
eaeb0ea @MostAwesomeDude Spawn app at views, start rebuilding views.
authored
91
f9a2d74 @MostAwesomeDude Get newsposts working again.
authored
92 return render_template("index.html", universes=universes,
93 newsposts=newsposts)
eaeb0ea @MostAwesomeDude Spawn app at views, start rebuilding views.
authored
94
20b44b9 @MostAwesomeDude views: Fix universe route.
authored
95 @app.route("/<universe:u>/")
34f992d @MostAwesomeDude Get cast wired up again, and attach Character to Universe.
authored
96 def universe(u):
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
97 comic = get_comic_query(u).order_by(Comic.id.desc()).first()
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
98
99 if comic is None:
ca03668 @MostAwesomeDude views: 404 if no comics found, use Flavor Text on RSS.
authored
100 abort(404)
101
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
102 comics = get_neighbors_for(u, comic)
c974418 @MostAwesomeDude Make navigation show up.
authored
103
68406ab @MostAwesomeDude views: Attempt to grab per-universe templates before the base templates.
authored
104 context = {
105 "u": u,
106 "comic": comic,
107 "comics": comics,
108 }
109
110 try:
111 return render_template("universe/%s/index.html" % u.slug, **context)
112 except TemplateNotFound:
113 return render_template("universe/index.html", **context)
a3d1196 @MostAwesomeDude Split main into a package.
authored
114
34f992d @MostAwesomeDude Get cast wired up again, and attach Character to Universe.
authored
115 @app.route("/<universe:u>/cast")
116 def cast(u):
117 # Re-add the universe to the session so that we can query it.
118 db.session.add(u)
119 characters = sorted(u.characters, key=attrgetter("name"))
68406ab @MostAwesomeDude views: Attempt to grab per-universe templates before the base templates.
authored
120
121 context = {
122 "u": u,
123 "characters": characters,
124 }
125
126 try:
127 return render_template("universe/%s/cast.html" % u.slug, **context)
128 except TemplateNotFound:
129 return render_template("universe/cast.html", **context)
82cb239 @MostAwesomeDude views, templates: Add cast page.
authored
130
3b4e459 @MostAwesomeDude views: Add recent-comic redirect view.
authored
131 @app.route("/<universe:u>/comics/recent")
132 def recent(u):
010c0ba @MostAwesomeDude views: Character filtering, part one.
authored
133 char = request.args.get("char", None)
134 if char is not None:
135 if char == "anything-goes":
136 # Get all characters that exist in this universe who have had any
137 # appearances. This could fail, in which case we bail with a cute
138 # error message.
139 q = Character.query.filter_by(universe=u)
140 chars = q.filter(Character.comics.any()).all()
141 if not chars:
142 flash("No characters have been introduced yet. Patience, "
143 "grasshopper.")
144 return redirect(url_for("universe", u=u))
145 char = choice(chars)
146 else:
147 char = Character.query.filter_by(universe=u, slug=char).first()
148 if not char:
149 flash("That character doesn't exist. I'm sorry, but we don't "
150 "have fanfiction-based characters in the story.")
151 return redirect(url_for("universe", u=u))
152 elif not char.comics:
153 flash("That character has not made an appearance yet. We "
154 "know how popular they are, though, so be prepared for "
155 "their debut!")
156 return redirect(url_for("universe", u=u))
157
158 q = get_comic_query(u)
159 if char:
160 q = q.filter(Comic.characters.contains(char))
161 comic = q.order_by(Comic.id.desc()).first()
162
163 if not comic:
164 # We probably shouldn't have been able to get here, in general. This
165 # could happen if there are no comics in this universe yet. However,
166 # it could possibly happen if there is some bug in the above
167 # character-sorting logic. Be aware of this when debugging this code
168 # later. :3
169 flash("No comics could be found. Something went wrong, perhaps?")
170 return redirect(url_for("index"))
171
172 if char:
173 return redirect(url_for("comics", u=u, cid=comic.id, char=char.slug))
174 else:
175 return redirect(url_for("comics", u=u, cid=comic.id))
3b4e459 @MostAwesomeDude views: Add recent-comic redirect view.
authored
176
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
177 @app.route("/<universe:u>/comics/<int:cid>")
178 def comics(u, cid):
a3d1196 @MostAwesomeDude Split main into a package.
authored
179 try:
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
180 comic = get_comic_query(u).filter_by(id=cid).one()
a3d1196 @MostAwesomeDude Split main into a package.
authored
181 except NoResultFound:
182 abort(404)
183
010c0ba @MostAwesomeDude views: Character filtering, part one.
authored
184 char = request.args.get("char", None)
185 if char is not None:
186 char = Character.query.filter_by(universe=u, slug=char).first()
187 if char is None:
188 flash("That character doesn't exist, and typing them into the "
189 "URL doesn't magically spring them into the comic. Sorry.")
190 elif char not in comic.characters:
191 flash("That character isn't in this particular comic, and won't "
192 "be, no matter how hard you wish. Sorry.")
193 char = None
194
5bdc181 @MostAwesomeDude Various universe passing fixes.
authored
195 comics = get_neighbors_for(u, comic)
c974418 @MostAwesomeDude Make navigation show up.
authored
196
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
197 previousq = get_comic_query(u).filter(Comic.position < comic.position)
198 nextq = get_comic_query(u).filter(Comic.position > comic.position)
a3d1196 @MostAwesomeDude Split main into a package.
authored
199
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
200 previous = previousq.order_by(Comic.position.desc()).first()
201 chrono = previous, nextq.order_by(Comic.position).first()
a3d1196 @MostAwesomeDude Split main into a package.
authored
202
203 cdict = {}
204
205 for character in list(comic.characters):
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
206 q = previousq.order_by(Comic.position.desc())
a3d1196 @MostAwesomeDude Split main into a package.
authored
207 previous = q.filter(Comic.characters.any(slug=character.slug)).first()
208
e372bfb @MostAwesomeDude views: Rewrite everything to handle future comics.
authored
209 next = nextq.filter(Comic.characters.any(slug=character.slug)).first()
a3d1196 @MostAwesomeDude Split main into a package.
authored
210
211 cdict[character.slug] = character, previous, next
212
68406ab @MostAwesomeDude views: Attempt to grab per-universe templates before the base templates.
authored
213 context = {
c465263 @MostAwesomeDude templates/comics: Enable logged-in comment form.
authored
214 "u": u,
a3d1196 @MostAwesomeDude Split main into a package.
authored
215 "comic": comic,
c974418 @MostAwesomeDude Make navigation show up.
authored
216 "comics": comics,
a3d1196 @MostAwesomeDude Split main into a package.
authored
217 "chrono": chrono,
218 "characters": cdict,
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
219 "ocform": CommentForm(),
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
220 }
a3d1196 @MostAwesomeDude Split main into a package.
authored
221
68406ab @MostAwesomeDude views: Attempt to grab per-universe templates before the base templates.
authored
222 try:
223 return render_template("universe/%s/comics.html" % u.slug, **context)
224 except TemplateNotFound:
225 return render_template("universe/comics.html", **context)
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
226
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
227 @app.route("/<universe:u>/comics/<int:cid>/comment", methods=("POST",))
228 def comment(u, cid):
11dc8a6 @MostAwesomeDude Implement comment form handling.
authored
229 if current_user.is_anonymous():
230 abort(403)
231
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
232 try:
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
233 comic = get_comic_query(u).filter_by(id=cid).one()
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
234 except NoResultFound:
235 abort(404)
236
237 form = CommentForm()
238
239 if form.validate_on_submit():
11dc8a6 @MostAwesomeDude Implement comment form handling.
authored
240 if form.anonymous.data:
241 name = "Anonymous"
242 else:
243 name = current_user.username
244
a3bd163 @MostAwesomeDude forms, views: Convince comment form to post.
authored
245 post = Post(name, form.comment.data, "", [None])
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
246 post.thread = comic.thread
247
a201d90 @MostAwesomeDude Make image uploads work for comments.
authored
248 image = form.datafile.file
249 if image:
250 post.file = os.path.join("comments", chan_filename(image))
251 filename = os.path.abspath(os.path.join("uploads", post.file))
252 image.save(filename)
253
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
254 db.session.add(post)
255 db.session.commit()
256
3d7abf5 @MostAwesomeDude More admin stuff. Make characters work.
authored
257 return redirect(url_for("comics", u=u, cid=cid))
6fd9d50 @MostAwesomeDude Reimplement comment form in a more customizeable manner.
authored
258
3e731ef @MostAwesomeDude util, views: Factor out RSS setup and add root RSS.
authored
259 @app.route("/rss.xml")
260 @cached
261 def rss():
e96a1f7 @MostAwesomeDude util, views: Sort RSS items according to date posted.
authored
262 comics = Comic.query.order_by(Comic.time.desc())[:10]
263 stuff = []
3e731ef @MostAwesomeDude util, views: Factor out RSS setup and add root RSS.
authored
264 for comic in comics:
0519e06 @MostAwesomeDude util, views: Fix RSS generation.
authored
265 url = url_for("comics", _external=True, u=comic.universe,
3e731ef @MostAwesomeDude util, views: Factor out RSS setup and add root RSS.
authored
266 cid=comic.id)
e96a1f7 @MostAwesomeDude util, views: Sort RSS items according to date posted.
authored
267 stuff.append((url, comic))
3e731ef @MostAwesomeDude util, views: Factor out RSS setup and add root RSS.
authored
268
269 link = url_for("index", _external=True)
270
271 return make_rss2(link, "DCoN", stuff)
272
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
273 @app.route("/<universe:u>/rss.xml")
af18555 @MostAwesomeDude Split decorators out, and start caching RSS.
authored
274 @cached
4a8c3f6 @MostAwesomeDude Start fixing up RSS.
authored
275 def universe_rss(u):
e96a1f7 @MostAwesomeDude util, views: Sort RSS items according to date posted.
authored
276 q = Comic.query.filter(Comic.universe == u).order_by(Comic.time.desc())
4a8c3f6 @MostAwesomeDude Start fixing up RSS.
authored
277 comics = q[:10]
e96a1f7 @MostAwesomeDude util, views: Sort RSS items according to date posted.
authored
278 stuff = []
ff1e213 @MostAwesomeDude Add RSS.
authored
279 for comic in comics:
3d7abf5 @MostAwesomeDude More admin stuff. Make characters work.
authored
280 url = url_for("comics", _external=True, u=u, cid=comic.id)
e96a1f7 @MostAwesomeDude util, views: Sort RSS items according to date posted.
authored
281 stuff.append((url, comic))
ff1e213 @MostAwesomeDude Add RSS.
authored
282
3d7abf5 @MostAwesomeDude More admin stuff. Make characters work.
authored
283 link = url_for("universe", _external=True, u=u)
e4ab59c @MostAwesomeDude views: Rewrite most of the views to take the universe as a param.
authored
284
3e731ef @MostAwesomeDude util, views: Factor out RSS setup and add root RSS.
authored
285 return make_rss2(link, u.title, stuff)
ff1e213 @MostAwesomeDude Add RSS.
authored
286
6a4aacb @MostAwesomeDude views, static: Add a real 404 handler.
authored
287 @app.errorhandler(404)
288 def not_found(error):
289 directory = os.path.join(app.root_path, "static/404")
290 filename = choice(os.listdir(directory))
291 image = os.path.join("404", filename)
292 return render_template("404.html", image=image)
5ab18bc @MostAwesomeDude Add 500 error handler.
authored
293
294 @app.errorhandler(500)
295 def internal_server_error(error):
296 return render_template("500.html")
Something went wrong with that request. Please try again.