Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 355 lines (279 sloc) 11.21 kB
ff1e213 @MostAwesomeDude Add RSS.
authored
1 from datetime import datetime
a369d15 @MostAwesomeDude views: Use HTTP basic auth to protect upload.
authored
2 from functools import wraps
a3d1196 @MostAwesomeDude Split main into a package.
authored
3 import os.path
4
ff1e213 @MostAwesomeDude Add RSS.
authored
5 from PyRSS2Gen import Guid, RSS2, RSSItem
6
a3d1196 @MostAwesomeDude Split main into a package.
authored
7 from sqlalchemy import func
8 from sqlalchemy.orm.exc import NoResultFound
9
10 from werkzeug import secure_filename
36152fa @MostAwesomeDude views: Use experimental Authenticator.
authored
11 from werkzeug.security import Authenticator
a3d1196 @MostAwesomeDude Split main into a package.
authored
12
82cb239 @MostAwesomeDude views, templates: Add cast page.
authored
13 from flask import abort, flash, redirect, render_template, request, url_for
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
14 from flaskext.login import login_user, logout_user
a3d1196 @MostAwesomeDude Split main into a package.
authored
15
16 from newrem.forms import (CharacterCreateForm, CharacterDeleteForm,
a46cb9b @MostAwesomeDude Add uploadable portraits.
authored
17 CharacterModifyForm, LoginForm, NewsForm, PortraitCreateForm,
18 PortraitModifyForm, RegisterForm, UploadForm)
a3d1196 @MostAwesomeDude Split main into a package.
authored
19 from newrem.main import app
a46cb9b @MostAwesomeDude Add uploadable portraits.
authored
20 from newrem.models import db, Character, Comic, Newspost, Portrait, User
a3d1196 @MostAwesomeDude Split main into a package.
authored
21
36152fa @MostAwesomeDude views: Use experimental Authenticator.
authored
22 authenticator = Authenticator({"hurp": "derp"})
a369d15 @MostAwesomeDude views: Use HTTP basic auth to protect upload.
authored
23
24 def auth_required(f):
25 @wraps(f)
26 def decorated(*args, **kwargs):
27 auth = request.authorization
36152fa @MostAwesomeDude views: Use experimental Authenticator.
authored
28 if not auth or not authenticator.validate(auth):
44be175 @MostAwesomeDude views: Use basic auth for now.
authored
29 return authenticator.make_basic_challenge("Cid's Lair",
36152fa @MostAwesomeDude views: Use experimental Authenticator.
authored
30 "Haha, no.")
a369d15 @MostAwesomeDude views: Use HTTP basic auth to protect upload.
authored
31 return f(*args, **kwargs)
32 return decorated
33
a3d1196 @MostAwesomeDude Split main into a package.
authored
34 @app.route("/characters")
7293a7b @MostAwesomeDude views: Secure character view.
authored
35 @auth_required
a3d1196 @MostAwesomeDude Split main into a package.
authored
36 def characters():
37 cform = CharacterCreateForm(prefix="create")
38 mform = CharacterModifyForm(prefix="modify")
39 dform = CharacterDeleteForm(prefix="delete")
40
41 return render_template("characters.html", cform=cform, mform=mform, dform=dform)
42
43 @app.route("/characters/create", methods=("POST",))
44 def characters_create():
45 form = CharacterCreateForm(prefix="create")
46
47 if form.validate_on_submit():
48 character = Character(form.name.data)
49 db.session.add(character)
50 db.session.commit()
0767de6 @MostAwesomeDude forms, models, views: Hack together character portraits.
authored
51
5e919e5 @MostAwesomeDude Fix up character portraits.
authored
52 path = os.path.abspath(os.path.join("uploads", character.portrait))
53 form.portrait.file.save(path)
0767de6 @MostAwesomeDude forms, models, views: Hack together character portraits.
authored
54
a3d1196 @MostAwesomeDude Split main into a package.
authored
55 flash("Successfully created character %s!" % character.name)
56 else:
57 flash("Couldn't validate form...")
58
59 return redirect(url_for("characters"))
60
61 @app.route("/characters/modify", methods=("POST",))
62 def characters_modify():
63 form = CharacterModifyForm(prefix="modify")
64
65 if form.validate_on_submit():
66 character = form.characters.data
67 if character:
0767de6 @MostAwesomeDude forms, models, views: Hack together character portraits.
authored
68 # Which modifications do we want to make?
69 if form.name.data:
70 character.rename(form.name.data)
71 db.session.add(character)
72 db.session.commit()
73 flash("Successfully renamed character %s!" % character.name)
74
75 if form.portrait.file:
5e919e5 @MostAwesomeDude Fix up character portraits.
authored
76 path = os.path.abspath(os.path.join("uploads",
77 character.portrait))
0767de6 @MostAwesomeDude forms, models, views: Hack together character portraits.
authored
78 form.portrait.file.save(path)
79 flash("Successfully changed portrait for character %s!" %
80 character.name)
a3d1196 @MostAwesomeDude Split main into a package.
authored
81 else:
82 flash("Couldn't find character for slug %s..." %
83 form.characters.data)
84 else:
85 flash("Couldn't validate form...")
86
87 return redirect(url_for("characters"))
88
89 @app.route("/characters/delete", methods=("POST",))
90 def characters_delete():
91 form = CharacterDeleteForm(prefix="delete")
92
93 if form.validate_on_submit():
94 character = form.characters.data
95 if character:
96 db.session.delete(character)
97 db.session.commit()
98 flash("Successfully removed character %s!" % character.name)
99 else:
100 flash("Couldn't find character for slug %s..." %
101 form.characters.data)
102 else:
103 flash("Couldn't validate form...")
104
105 return redirect(url_for("characters"))
106
a46cb9b @MostAwesomeDude Add uploadable portraits.
authored
107 @app.route("/portraits")
108 @auth_required
109 def portraits():
110 cform = PortraitCreateForm(prefix="create")
111 mform = PortraitModifyForm(prefix="modify")
112
113 return render_template("portraits.html", cform=cform, mform=mform)
114
115 @app.route("/portraits/create", methods=("POST",))
116 def portraits_create():
117 form = PortraitCreateForm(prefix="create")
118
119 if form.validate_on_submit():
120 portrait = Portrait(form.name.data)
121 db.session.add(portrait)
122 db.session.commit()
123
124 path = os.path.abspath(os.path.join("uploads", portrait.portrait))
125 form.portrait.file.save(path)
126
127 flash("Successfully created portrait %s!" % portrait.name)
128 else:
129 flash("Couldn't validate form...")
130
131 return redirect(url_for("portraits"))
132
133 @app.route("/portraits/modify", methods=("POST",))
134 def portraits_modify():
135 form = PortraitModifyForm(prefix="modify")
136
137 if form.validate_on_submit():
138 portrait = form.portraits.data
139 if portrait and form.portrait.file:
140 path = os.path.abspath(os.path.join("uploads", portrait.portrait))
141 form.portrait.file.save(path)
142 flash("Successfully changed portrait for portrait %s!" %
143 portrait.name)
144 else:
145 flash("Couldn't find portrait for slug %s..." %
146 form.portraits.data)
147 else:
148 flash("Couldn't validate form...")
149
150 return redirect(url_for("portraits"))
151
6f591fd @MostAwesomeDude Make newsposts uploadable.
authored
152 @app.route("/news", methods=("GET", "POST"))
153 @auth_required
154 def news():
155 form = NewsForm()
156
157 if form.validate_on_submit():
158 post = Newspost(form.title.data, form.content.data)
a46cb9b @MostAwesomeDude Add uploadable portraits.
authored
159 post.portrait = form.portrait.data
6f591fd @MostAwesomeDude Make newsposts uploadable.
authored
160 db.session.add(post)
161 db.session.commit()
162 return redirect(url_for("index"))
163
164 return render_template("news.html", form=form)
165
a3d1196 @MostAwesomeDude Split main into a package.
authored
166 @app.route("/upload", methods=("GET", "POST"))
a369d15 @MostAwesomeDude views: Use HTTP basic auth to protect upload.
authored
167 @auth_required
a3d1196 @MostAwesomeDude Split main into a package.
authored
168 def upload():
169 form = UploadForm()
170
171 if form.validate_on_submit():
172 d = dict((c.name, c) for c in
173 Character.query.order_by(Character.name).all())
174 try:
175 characters = [d[name] for name in form.characters.data]
176 except KeyError, ke:
177 flash("Couldn't find character %s..." % ke.args)
178 return render_template("upload.html", form=form)
179
180 bottom = db.session.query(func.min(Comic.position)).first()[0]
181 top = db.session.query(func.max(Comic.position)).first()[0]
182
183 if (bottom and top and form.index.data and
184 not bottom <= form.index.data <= top):
185 flash("Couldn't find insertion point between %d and %d"
186 % (bottom, top))
187 return render_template("upload.html", form=form)
188
6821d93 @MostAwesomeDude views: Place comics into the correct folder on upload.
authored
189 filename = os.path.join("comics",
190 secure_filename(form.file.file.filename))
a3d1196 @MostAwesomeDude Split main into a package.
authored
191 path = os.path.abspath(os.path.join("uploads", filename))
192 if os.path.exists(path):
193 flash("File already exists!")
194 return render_template("upload.html", form=form)
195
196 form.file.file.save(path)
197 comic = Comic(filename)
198 comic.characters = characters
ad4381b @MostAwesomeDude forms, views, templates: Add comic title field support.
authored
199 comic.title = form.title.data
a3d1196 @MostAwesomeDude Split main into a package.
authored
200
201 if form.index.data == 0:
202 comic.insert_at_head()
203 else:
204 prior = Comic.query.filter(Comic.position < form.index.data).first()
205 comic.insert(prior)
206
207 db.session.add(comic)
208 db.session.commit()
209 return redirect(url_for("comics", cid=comic.id))
210
211 return render_template("upload.html", form=form)
212
213 @app.errorhandler(404)
214 def not_found(error):
215 return "Couldn't find the page!", 404
216
c974418 @MostAwesomeDude Make navigation show up.
authored
217 def get_neighbors_for(comic):
218 """
219 Grab the comics around a given comic.
220 """
221
222 comics = {}
223
224 q = Comic.query.filter(Comic.time < comic.time)
225 a = q.order_by(Comic.time.desc()).first()
226 b = q.order_by(Comic.time).first()
227 q = Comic.query.filter(Comic.time > comic.time)
228 c = q.order_by(Comic.time).first()
229 d = q.order_by(Comic.time.desc()).first()
230
231 comics["upload"] = a, b, c, d
232
233 return comics
234
a3d1196 @MostAwesomeDude Split main into a package.
authored
235 @app.route("/")
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
236 def index():
5ad2301 @MostAwesomeDude templates, views: Make most recent comic show up on front page.
authored
237 comic = Comic.query.order_by(Comic.id.desc()).first()
c974418 @MostAwesomeDude Make navigation show up.
authored
238 comics = get_neighbors_for(comic)
239
da1b843 @MostAwesomeDude templates, views: Add newsposts to index page.
authored
240 newsposts = Newspost.query.order_by(Newspost.time.desc())[:5]
c974418 @MostAwesomeDude Make navigation show up.
authored
241 return render_template("index.html", comic=comic, comics=comics,
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
242 newsposts=newsposts)
a3d1196 @MostAwesomeDude Split main into a package.
authored
243
82cb239 @MostAwesomeDude views, templates: Add cast page.
authored
244 @app.route("/cast")
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
245 def cast():
82cb239 @MostAwesomeDude views, templates: Add cast page.
authored
246 characters = Character.query.order_by(Character.name)
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
247 return render_template("cast.html", characters=characters)
82cb239 @MostAwesomeDude views, templates: Add cast page.
authored
248
a3d1196 @MostAwesomeDude Split main into a package.
authored
249 @app.route("/comics/")
250 def comics_root():
251 return redirect(url_for("comics", cid=1))
252
253 @app.route("/comics/<int:cid>")
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
254 def comics(cid):
a3d1196 @MostAwesomeDude Split main into a package.
authored
255 try:
40bf48b @MostAwesomeDude views: Tweak ORM queries for speed.
authored
256 comic = Comic.query.filter_by(id=cid).one()
a3d1196 @MostAwesomeDude Split main into a package.
authored
257 except NoResultFound:
258 abort(404)
259
c974418 @MostAwesomeDude Make navigation show up.
authored
260 comics = get_neighbors_for(comic)
261
a3d1196 @MostAwesomeDude Split main into a package.
authored
262 q = Comic.query.filter(Comic.position < comic.position)
263 previous = q.order_by(Comic.position.desc()).first()
264
265 q = Comic.query.filter(Comic.position > comic.position)
266 chrono = previous, q.order_by(Comic.position).first()
267
268 cdict = {}
269
270 for character in list(comic.characters):
271 q = Comic.query.filter(Comic.position < comic.position)
272 q = q.order_by(Comic.position.desc())
273 previous = q.filter(Comic.characters.any(slug=character.slug)).first()
274
275 q = Comic.query.filter(Comic.position > comic.position)
276 next = q.filter(Comic.characters.any(slug=character.slug)).first()
277
278 cdict[character.slug] = character, previous, next
279
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
280 kwargs = {
a3d1196 @MostAwesomeDude Split main into a package.
authored
281 "comic": comic,
c974418 @MostAwesomeDude Make navigation show up.
authored
282 "comics": comics,
a3d1196 @MostAwesomeDude Split main into a package.
authored
283 "chrono": chrono,
284 "characters": cdict,
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
285 }
a3d1196 @MostAwesomeDude Split main into a package.
authored
286
287 return render_template("comics.html", **kwargs)
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
288
ff1e213 @MostAwesomeDude Add RSS.
authored
289 @app.route("/rss.xml")
290 def rss():
291 comics = Comic.query.order_by(Comic.id.desc())[:10]
292 items = []
293 for comic in comics:
294 url = url_for("comics", cid=comic.id)
295 item = RSSItem(title=comic.title, link=url, description=comic.title,
296 guid=Guid(url), pubDate=comic.time)
297 items.append(item)
298
299 rss2 = RSS2(title="RSS", link=url_for("index"), description="Hurp!",
300 lastBuildDate=datetime.utcnow(), items=items)
301 return rss2.to_xml(encoding="utf8")
302
986d7de @MostAwesomeDude forms, views: Add registration page.
authored
303 @app.route("/register", methods=("GET", "POST"))
304 def register():
305 form = RegisterForm()
306
307 if form.validate_on_submit():
308 user = User.query.filter_by(username=form.username.data).first()
309
310 if not user:
311 user = User(form.username.data, form.password.data)
312 db.session.add(user)
313 user.login()
314 login_user(user, remember=True)
315 flash("Logged in!")
316 if "next" in request.args:
317 return redirect(request.args["next"])
318 else:
319 return redirect(url_for("index"))
320
321 return render_template("register.html", form=form)
322
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
323 @app.route("/login", methods=("GET", "POST"))
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
324 def login():
325 form = LoginForm()
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
326
327 if form.validate_on_submit():
328 user = User.query.filter_by(username=form.username.data).first()
329
e645956 @MostAwesomeDude views: Flash errors on invalid login.
authored
330 if user:
331 if user.check_password(form.password.data):
332 user.login()
333 login_user(user, remember=True)
334 flash("Logged in!")
335 if "next" in request.args:
336 return redirect(request.args["next"])
337 else:
338 return redirect(url_for("index"))
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
339 else:
e645956 @MostAwesomeDude views: Flash errors on invalid login.
authored
340 flash("Incorrect password!")
341 else:
342 flash("No user %s found!" % form.username.data)
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
343
2f2b4d0 @MostAwesomeDude views: Nuke with_login_form().
authored
344 return render_template("login.html", login_form=form)
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
345
346 @app.route("/logout")
347 def logout():
348 logout_user()
a369d15 @MostAwesomeDude views: Use HTTP basic auth to protect upload.
authored
349 flash("Logged out!")
6115178 @MostAwesomeDude Add users and login/logout, as well as status.
authored
350
351 if "next" in request.args:
352 return redirect(request.args["next"])
353 else:
354 return redirect(url_for("index"))
Something went wrong with that request. Please try again.