Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 426 lines (368 sloc) 14.189 kb
bd6ca08 mediarss
Benjamin Golub authored
1 import BeautifulSoup
c71b525 started to worki
Benjamin Golub authored
2 import functools
fca0ed6 ping
Benjamin Golub authored
3 import hashlib
24bfc91 os
Benjamin Golub authored
4 import os
c71b525 started to worki
Benjamin Golub authored
5 import re
6 import tornado.web
7 import tornado.wsgi
8 import unicodedata
fca0ed6 ping
Benjamin Golub authored
9 import urllib
0b6cbf3 missing import
Benjamin Golub authored
10 import uuid
c71b525 started to worki
Benjamin Golub authored
11 import wsgiref.handlers
12
9959106 pretty print
Benjamin Golub authored
13 from django.utils import simplejson as json
14
a324d06 stylistic nits
Benjamin Golub authored
15 from google.appengine.ext import db
16 from google.appengine.api import urlfetch
17 from google.appengine.api import users
2cb2b18 feeds
Benjamin Golub authored
18
c71b525 started to worki
Benjamin Golub authored
19
20 def administrator(method):
21 @functools.wraps(method)
22 def wrapper(self, *args, **kwargs):
23 user = users.get_current_user()
24 if not user:
25 if self.request.method == "GET":
26 self.redirect(users.create_login_url(self.request.uri))
27 return
28 raise tornado.web.HTTPError(403)
29 elif not users.is_current_user_admin():
30 raise tornado.web.HTTPError(403)
31 else:
32 return method(self, *args, **kwargs)
33 return wrapper
34
35
a324d06 stylistic nits
Benjamin Golub authored
36 class Entry(db.Model):
37 author = db.UserProperty()
38 title = db.StringProperty(required=True)
39 slug = db.StringProperty(required=True)
40 body = db.TextProperty(required=True)
41 published = db.DateTimeProperty(auto_now_add=True)
42 updated = db.DateTimeProperty(auto_now=True)
43 tags = db.ListProperty(db.Category)
4c2e0ae hidden entries
Benjamin Golub authored
44 hidden = db.BooleanProperty(default=False)
a324d06 stylistic nits
Benjamin Golub authored
45
46
c71b525 started to worki
Benjamin Golub authored
47 class BaseHandler(tornado.web.RequestHandler):
48 def get_current_user(self):
49 user = users.get_current_user()
50 if user:
51 user.administrator = users.is_current_user_admin()
52 return user
53
54 def get_integer_argument(self, name, default):
55 try:
56 return int(self.get_argument(name, default))
57 except (TypeError, ValueError):
58 return default
59
60 def render_string(self, template_name, **kwargs):
61 return tornado.web.RequestHandler.render_string(self, template_name,
62 users=users, **kwargs)
63
2cb2b18 feeds
Benjamin Golub authored
64 def render(self, template_name, **kwargs):
65 format = self.get_argument("format", None)
d4d4ba7 move feed.html to atom.xml, force evaluate db.Query objects before re…
Benjamin Golub authored
66 if "entries" in kwargs and isinstance(kwargs["entries"], db.Query):
67 # Force evaluate queries so we know if there are entries before
68 # trying to render a feed
69 kwargs["entries"] = list(kwargs["entries"])
70 if kwargs.get("entries") and format == "atom":
2cb2b18 feeds
Benjamin Golub authored
71 self.set_header("Content-Type", "application/atom+xml")
10ac002 SUP header on HEAD requests
Benjamin Golub authored
72 self.set_sup_header()
d4d4ba7 move feed.html to atom.xml, force evaluate db.Query objects before re…
Benjamin Golub authored
73 template_name = "atom.xml"
970eb38 json for exporting
Benjamin Golub authored
74 if "entries" in kwargs and format == "json":
75 json_entries = [{
76 "title": entry.title,
77 "slug": entry.slug,
78 "body": entry.body,
79 "author": entry.author.nickname(),
80 "published": entry.published.isoformat(),
81 "updated": entry.updated.isoformat(),
82 "tags": entry.tags,
c08795c new blog structre
Benjamin Golub authored
83 "link": "http://" + self.request.host + "/" + entry.slug,
970eb38 json for exporting
Benjamin Golub authored
84 } for entry in kwargs["entries"]]
9959106 pretty print
Benjamin Golub authored
85 data = {
0f5db2b show cursor in json output
Benjamin Golub authored
86 "entries": json_entries,
87 }
88 if "cursor" in kwargs:
9959106 pretty print
Benjamin Golub authored
89 data["cursor"] = kwargs["cursor"]
970eb38 json for exporting
Benjamin Golub authored
90 self.set_header("Content-Type", "text/javascript")
9959106 pretty print
Benjamin Golub authored
91 self.write(json.dumps(data, sort_keys=True, indent=4) if
92 self.get_argument("pretty", False) else data)
970eb38 json for exporting
Benjamin Golub authored
93 return
2cb2b18 feeds
Benjamin Golub authored
94 return tornado.web.RequestHandler.render(self, template_name, **kwargs)
95
d0d2c26 write tags
Benjamin Golub authored
96 def slugify(self, value):
97 slug = unicodedata.normalize("NFKD", value).encode(
98 "ascii", "ignore")
99 slug = re.sub(r"[^\w]+", " ", slug)
100 return "-".join(slug.lower().strip().split())
101
fca0ed6 ping
Benjamin Golub authored
102 def generate_sup_id(self, url=None):
103 return hashlib.md5(url or self.request.full_url()).hexdigest()[:10]
104
10ac002 SUP header on HEAD requests
Benjamin Golub authored
105 def set_sup_header(self, url=None):
106 sup_id = self.generate_sup_id(url)
107 self.set_header("X-SUP-ID",
108 "http://friendfeed.com/api/public-sup.json#" + sup_id)
109
fca0ed6 ping
Benjamin Golub authored
110 def ping(self):
cacdca1 leave a note about urlfetch
Benjamin Golub authored
111 # Swallow exceptions when pinging, urlfetch can be unstable and it
112 # isn't the end of the world if a ping doesn't make it. Since we don't
113 # care about the response, in an ideal world, the urlfetch API would
114 # have an option to perform all of this asynchronously and optionally
115 # specify a number of retries
fca0ed6 ping
Benjamin Golub authored
116 feed = "http://" + self.request.host + "/?format=atom"
117 args = urllib.urlencode({
118 "name": self.application.settings["blog_title"],
119 "url": "http://" + self.request.host + "/",
120 "changesURL": feed,
121 })
43c405d tweaks
Benjamin Golub authored
122 try:
123 urlfetch.fetch("http://blogsearch.google.com/ping?" + args)
124 except:
125 pass
fca0ed6 ping
Benjamin Golub authored
126 args = urllib.urlencode({
127 "url": feed,
128 "supid": self.generate_sup_id(feed),
129 })
43c405d tweaks
Benjamin Golub authored
130 try:
131 urlfetch.fetch("http://friendfeed.com/api/public-sup-ping?" + args)
132 except:
133 pass
fca0ed6 ping
Benjamin Golub authored
134 args = urllib.urlencode({
135 "bloglink": "http://" + self.request.host + "/",
136 })
43c405d tweaks
Benjamin Golub authored
137 try:
138 urlfetch.fetch("http://www.feedburner.com/fb/a/pingSubmit?" + args)
139 except:
140 pass
eee3f60 stop using django feedgenerator
Benjamin Golub authored
141 args = urllib.urlencode({
142 "hub.mode": "publish",
143 "hub.url": feed,
144 })
145 headers = {
146 "Content-Type": "application/x-www-form-urlencoded",
147 }
148 try:
149 result = urlfetch.fetch("http://pubsubhubbub.appspot.com/",
150 payload=args, method=urlfetch.POST, headers=headers)
151 except:
152 pass
fca0ed6 ping
Benjamin Golub authored
153
6eec54a up-to-date with tornado changes
Benjamin Golub authored
154 def get_error_html(self, status_code, **kwargs):
d0700ec 404
Benjamin Golub authored
155 if status_code == 404:
156 self.write(self.render_string("404.html"))
157 else:
6eec54a up-to-date with tornado changes
Benjamin Golub authored
158 return tornado.web.RequestHandler.get_error_html(self, status_code,
159 **kwargs)
d0700ec 404
Benjamin Golub authored
160
10ac002 SUP header on HEAD requests
Benjamin Golub authored
161 def head(self, *args):
162 if self.get_argument("format", None) == "atom":
163 self.set_sup_header()
2bf2270 404
Benjamin Golub authored
164
c71b525 started to worki
Benjamin Golub authored
165
166 class HomeHandler(BaseHandler):
167 def get(self):
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
168 q = db.Query(Entry).filter("hidden =", False).order("-published")
169 cursor = self.get_argument("cursor", None)
170 if cursor:
973437f catch errors
Benjamin Golub authored
171 try:
172 q.with_cursor(cursor)
173 except (db.BadRequestError, db.BadValueError):
174 cursor = None
1dfdd95 configurable limits, no paging link on last page
Benjamin Golub authored
175 limit = self.application.settings.get("num_home", 5)
176 entries = q.fetch(limit=limit)
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
177 if not cursor:
178 self.recent_entries = entries
1dfdd95 configurable limits, no paging link on last page
Benjamin Golub authored
179 cursor = q.cursor() if len(entries) == limit else None
180 self.render("home.html", entries=entries, cursor=cursor)
c71b525 started to worki
Benjamin Golub authored
181
182
84c6a0b forgot to ci this a while ago
Benjamin Golub authored
183 class AboutHandler(BaseHandler):
8073377 remove slashes on some user facing pages
Benjamin Golub authored
184 @tornado.web.removeslash
84c6a0b forgot to ci this a while ago
Benjamin Golub authored
185 def get(self):
186 self.render("about.html")
187
188
c71b525 started to worki
Benjamin Golub authored
189 class ArchiveHandler(BaseHandler):
8073377 remove slashes on some user facing pages
Benjamin Golub authored
190 @tornado.web.removeslash
c71b525 started to worki
Benjamin Golub authored
191 def get(self):
4cc69ba cursor for the archive handler
Benjamin Golub authored
192 q = db.Query(Entry).filter("hidden =", False).order("-published")
193 cursor = self.get_argument("cursor", None)
194 if cursor:
195 try:
196 q.with_cursor(cursor)
197 except (db.BadRequestError, db.BadValueError):
198 cursor = None
1dfdd95 configurable limits, no paging link on last page
Benjamin Golub authored
199 limit = self.application.settings.get("num_archive", 10)
200 entries = q.fetch(limit=limit)
4cc69ba cursor for the archive handler
Benjamin Golub authored
201 if not cursor:
202 self.recent_entries = entries[:5]
1dfdd95 configurable limits, no paging link on last page
Benjamin Golub authored
203 cursor = q.cursor() if len(entries) == limit else None
204 self.render("archive.html", entries=entries, cursor=cursor)
c71b525 started to worki
Benjamin Golub authored
205
206
207 class ComposeHandler(BaseHandler):
208 @administrator
209 def get(self):
210 key = self.get_argument("key", None)
211 try:
212 entry = Entry.get(key) if key else None
213 except db.BadKeyError:
214 entry = None
215 self.render("compose.html", entry=entry)
216
217 @administrator
218 def post(self):
219 key = self.get_argument("key", None)
220 if key:
221 try:
222 entry = Entry.get(key)
223 except db.BadKeyError:
224 self.redirect("/")
225 return
226 entry.body = self.get_argument("body")
d0d2c26 write tags
Benjamin Golub authored
227 entry.title = self.get_argument("title")
c71b525 started to worki
Benjamin Golub authored
228 else:
229 title = self.get_argument("title")
d0d2c26 write tags
Benjamin Golub authored
230 slug = self.slugify(title)
c71b525 started to worki
Benjamin Golub authored
231 if not slug:
232 slug = "entry"
233 original_slug = slug
234 while db.Query(Entry).filter("slug = ", slug).get():
235 slug = original_slug + "-" + uuid.uuid4().hex[:2]
236 entry = Entry(
237 author=self.current_user,
238 body=self.get_argument("body"),
d0d2c26 write tags
Benjamin Golub authored
239 slug=slug,
240 title=title,
c71b525 started to worki
Benjamin Golub authored
241 )
d0d2c26 write tags
Benjamin Golub authored
242 tags = set([self.slugify(unicode(tag)) for tag in
243 self.get_argument("tags", "").split(",")])
244 tags = [db.Category(tag) for tag in tags if tag]
3f01b94 fix so you can remove all tags from an entry
Benjamin Golub authored
245 entry.tags = tags
074c5d2 allow hiding while composing
Benjamin Golub authored
246 entry.hidden = bool(self.get_argument("hidden", False))
c71b525 started to worki
Benjamin Golub authored
247 entry.put()
074c5d2 allow hiding while composing
Benjamin Golub authored
248 if not key and not entry.hidden:
fca0ed6 ping
Benjamin Golub authored
249 self.ping()
c08795c new blog structre
Benjamin Golub authored
250 self.redirect("/" + entry.slug)
c71b525 started to worki
Benjamin Golub authored
251
252
34760ec delete
Benjamin Golub authored
253 class DeleteHandler(BaseHandler):
254 @administrator
255 def get(self):
256 key = self.get_argument("key")
257 try:
258 entry = Entry.get(key)
259 except db.BadKeyError:
260 raise tornado.web.HTTPError(404)
261 self.render("delete.html", entry=entry)
262
263 @administrator
264 def post(self):
265 key = self.get_argument("key")
266 try:
267 entry = Entry.get(key)
268 except db.BadKeyError:
269 raise tornado.web.HTTPError(404)
270 entry.delete()
271 self.redirect("/")
272
273
4c2e0ae hidden entries
Benjamin Golub authored
274 class HideHandler(BaseHandler):
275 @administrator
276 def get(self):
277 key = self.get_argument("key")
278 try:
279 entry = Entry.get(key)
280 except db.BadKeyError:
281 raise tornado.web.HTTPError(404)
282 self.render("hide.html", entry=entry)
283
284 @administrator
285 def post(self):
286 key = self.get_argument("key")
287 try:
288 entry = Entry.get(key)
289 except db.BadKeyError:
290 raise tornado.web.HTTPError(404)
66c376d unhide
Benjamin Golub authored
291 entry.hidden = not bool(self.get_argument("unhide", False))
4c2e0ae hidden entries
Benjamin Golub authored
292 entry.put()
293 self.redirect("/")
294
295
c08795c new blog structre
Benjamin Golub authored
296 class OldEntryHandler(BaseHandler):
8073377 remove slashes on some user facing pages
Benjamin Golub authored
297 @tornado.web.removeslash
c71b525 started to worki
Benjamin Golub authored
298 def get(self, slug):
c08795c new blog structre
Benjamin Golub authored
299 self.redirect("/" + slug)
c71b525 started to worki
Benjamin Golub authored
300
c08795c new blog structre
Benjamin Golub authored
301 @tornado.web.removeslash
2bf2270 404
Benjamin Golub authored
302 def head(self, slug):
c08795c new blog structre
Benjamin Golub authored
303 return self.get(slug)
304
c71b525 started to worki
Benjamin Golub authored
305
9f8684e tag reading
Benjamin Golub authored
306 class TagHandler(BaseHandler):
8073377 remove slashes on some user facing pages
Benjamin Golub authored
307 @tornado.web.removeslash
9f8684e tag reading
Benjamin Golub authored
308 def get(self, tag):
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
309 q = db.Query(Entry).filter("hidden =", False).filter("tags =", tag)
310 q.order("-published")
311 self.render("tag.html", entries=q, tag=tag)
9f8684e tag reading
Benjamin Golub authored
312
a324d06 stylistic nits
Benjamin Golub authored
313
d0700ec 404
Benjamin Golub authored
314 class CatchAllHandler(BaseHandler):
c08795c new blog structre
Benjamin Golub authored
315 def __init__(self, *args, **kwargs):
316 BaseHandler.__init__(self, *args, **kwargs)
317 self.entry = None
318 slug = self.request.path[1:]
319 if slug:
320 self.entry = db.Query(Entry).filter("slug =", slug).get()
321
8073377 remove slashes on some user facing pages
Benjamin Golub authored
322 @tornado.web.removeslash
d0700ec 404
Benjamin Golub authored
323 def get(self):
c08795c new blog structre
Benjamin Golub authored
324 if self.entry:
325 return self.render("entry.html", entry=self.entry,
326 entries=[self.entry])
84c6a0b forgot to ci this a while ago
Benjamin Golub authored
327 self.set_status(404)
d0700ec 404
Benjamin Golub authored
328 self.render("404.html")
329
2bf2270 404
Benjamin Golub authored
330 def head(self):
c08795c new blog structre
Benjamin Golub authored
331 if not self.entry:
332 self.set_status(404)
2bf2270 404
Benjamin Golub authored
333
9f8684e tag reading
Benjamin Golub authored
334
c71b525 started to worki
Benjamin Golub authored
335 class EntryModule(tornado.web.UIModule):
56a60dc remove some unused files/features, replace disqus comments with faceb…
Benjamin Golub authored
336 def render(self, entry, show_comments=False):
c71b525 started to worki
Benjamin Golub authored
337 self.show_comments = show_comments
338 return self.render_string("modules/entry.html", entry=entry,
56a60dc remove some unused files/features, replace disqus comments with faceb…
Benjamin Golub authored
339 show_comments=show_comments)
c71b525 started to worki
Benjamin Golub authored
340
341 def javascript_files(self):
342 if self.show_comments:
1346bf9 thanks John Tantalo
Benjamin Golub authored
343 return ["http://connect.facebook.net/en_US/all.js#appId=" +
344 self.handler.application.settings["fb_app_id"] + "&xfbml=1"]
c71b525 started to worki
Benjamin Golub authored
345 return None
346
347
bd6ca08 mediarss
Benjamin Golub authored
348 class MediaRSSModule(tornado.web.UIModule):
349 def render(self, entry):
566d8e5 faster
Benjamin Golub authored
350 soup = BeautifulSoup.BeautifulSoup(entry.body,
516549f nit
Benjamin Golub authored
351 parseOnlyThese=BeautifulSoup.SoupStrainer("img"))
bd6ca08 mediarss
Benjamin Golub authored
352 imgs = soup.findAll("img")
353 thumbnails = []
354 for img in imgs:
355 if "nomediarss" in img.get("class", "").split():
356 continue
357 thumbnails.append({
358 "url": img["src"],
359 "title": img.get("title", img.get("alt", "")),
360 "width": img.get("width", ""),
361 "height": img.get("height", ""),
362 })
363 return self.render_string("modules/mediarss.html", entry=entry,
364 thumbnails=thumbnails)
365
366
7c5ca9d EntrySmallModule for when you just need a title/date
Benjamin Golub authored
367 class EntrySmallModule(tornado.web.UIModule):
368 def render(self, entry, show_date=False):
369 return self.render_string("modules/entry-small.html", entry=entry,
370 show_date=show_date)
371
372
c71b525 started to worki
Benjamin Golub authored
373 class RecentEntriesModule(tornado.web.UIModule):
374 def render(self):
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
375 entries = getattr(self.handler, "recent_entries", None)
376 if not entries:
377 q = db.Query(Entry).filter("hidden =", False).order("-published")
378 entries = q.fetch(limit=5)
c71b525 started to worki
Benjamin Golub authored
379 return self.render_string("modules/recententries.html", entries=entries)
380
381
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
382 class NavigationModule(tornado.web.UIModule):
383 def render(self, cursor):
384 kwargs = {
385 "cursor": cursor,
386 }
387 previous = self.request.path + "?" + urllib.urlencode(kwargs)
388 return self.render_string("modules/navigation.html", previous=previous)
389
390
c71b525 started to worki
Benjamin Golub authored
391 settings = {
43c405d tweaks
Benjamin Golub authored
392 "blog_author": "Benjamin Golub",
393 "blog_title": "Benjamin Golub",
71064bf fbadmins
Benjamin Golub authored
394 "fb_admins": "15500414",
56a60dc remove some unused files/features, replace disqus comments with faceb…
Benjamin Golub authored
395 "fb_app_id": "143871635676545",
c71b525 started to worki
Benjamin Golub authored
396 "debug": os.environ.get("SERVER_SOFTWARE", "").startswith("Development/"),
397 "template_path": os.path.join(os.path.dirname(__file__), "templates"),
398 "ui_modules": {
399 "Entry": EntryModule,
7c5ca9d EntrySmallModule for when you just need a title/date
Benjamin Golub authored
400 "EntrySmall": EntrySmallModule,
bd6ca08 mediarss
Benjamin Golub authored
401 "MediaRSS": MediaRSSModule,
c71b525 started to worki
Benjamin Golub authored
402 "RecentEntries": RecentEntriesModule,
d58c4b3 pagination with cursors, 80 column cleanup
Benjamin Golub authored
403 "Navigation": NavigationModule,
c71b525 started to worki
Benjamin Golub authored
404 },
405 "xsrf_cookies": True,
406 }
407
408 application = tornado.wsgi.WSGIApplication([
409 (r"/", HomeHandler),
8073377 remove slashes on some user facing pages
Benjamin Golub authored
410 (r"/about/?", AboutHandler),
411 (r"/archive/?", ArchiveHandler),
c71b525 started to worki
Benjamin Golub authored
412 (r"/compose", ComposeHandler),
34760ec delete
Benjamin Golub authored
413 (r"/delete", DeleteHandler),
c08795c new blog structre
Benjamin Golub authored
414 (r"/e/([\w-]+)/?", OldEntryHandler),
6348cfc something continues to fetch this; redirect them
Benjamin Golub authored
415 (r"/feed/?", tornado.web.RedirectHandler, {"url": "/?format=atom"}),
4c2e0ae hidden entries
Benjamin Golub authored
416 (r"/hide", HideHandler),
8073377 remove slashes on some user facing pages
Benjamin Golub authored
417 (r"/t/([\w-]+)/?", TagHandler),
d0700ec 404
Benjamin Golub authored
418 (r".*", CatchAllHandler),
c71b525 started to worki
Benjamin Golub authored
419 ], **settings)
420
421 def main():
422 wsgiref.handlers.CGIHandler().run(application)
423
424 if __name__ == "__main__":
425 main()
Something went wrong with that request. Please try again.