PageRenderTime 72ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/app.py

https://github.com/haldun/bookmarks
Python | 292 lines | 242 code | 45 blank | 5 comment | 31 complexity | 7039b122da305289c599b2dbc0ea6db7 MD5 | raw file
  1. # Python imports
  2. import collections
  3. import datetime
  4. import logging
  5. import os
  6. # Tornado imports
  7. import tornado.auth
  8. import tornado.httpserver
  9. import tornado.ioloop
  10. import tornado.options
  11. import tornado.web
  12. from tornado.options import define, options
  13. from tornado.web import url
  14. import mongoengine
  15. import pylibmc
  16. import yaml
  17. import pymongo
  18. from pymongo.objectid import ObjectId
  19. # App imports
  20. import forms
  21. import importer
  22. import uimodules
  23. import util
  24. # Options
  25. define("port", default=8888, type=int)
  26. define("config_file", default="app_config.yml", help="app_config file")
  27. class Application(tornado.web.Application):
  28. def __init__(self):
  29. handlers = [
  30. url(r'/', IndexHandler, name='index'),
  31. url(r'/auth/google', GoogleAuthHandler, name='auth_google'),
  32. url(r'/logout', LogoutHandler, name='logout'),
  33. url(r'/home', HomeHandler, name='home'),
  34. url(r'/import', ImportHandler, name='import'),
  35. url(r'/edit/(?P<id>\w+)', EditBookmarkHandler, name='edit'),
  36. url(r'/new', NewBookmarkHandler, name='new'),
  37. url(r'/b', BookmarkletHandler, name='bookmarklet'),
  38. url(r'/tags', TagsHandler, name='tags'),
  39. url(r'/delete_multi', DeleteMultipleBookmarksHandler, name='delete_multi'),
  40. ]
  41. settings = dict(
  42. debug=self.config.debug,
  43. login_url='/auth/google',
  44. static_path=os.path.join(os.path.dirname(__file__), "static"),
  45. template_path=os.path.join(os.path.dirname(__file__), 'templates'),
  46. xsrf_cookies=True,
  47. cookie_secret=self.config.cookie_secret,
  48. ui_modules=uimodules,
  49. )
  50. tornado.web.Application.__init__(self, handlers, **settings)
  51. self.connection = pymongo.Connection()
  52. self.db = self.connection[self.config.mongodb_database]
  53. # Create pymongo indexes
  54. self.db.users.ensure_index('email')
  55. self.db.bookmarks.ensure_index('user')
  56. self.db.bookmarks.ensure_index([('user', pymongo.DESCENDING),
  57. ('url_digest', pymongo.DESCENDING)])
  58. self.db.tags.ensure_index('user')
  59. @property
  60. def config(self):
  61. if not hasattr(self, '_config'):
  62. logging.debug("Loading app config")
  63. stream = file(options.config_file, 'r')
  64. self._config = tornado.web._O(yaml.load(stream))
  65. return self._config
  66. @property
  67. def memcache(self):
  68. if not hasattr(self, '_memcache'):
  69. self._memcache = pylibmc.Client(
  70. self.config.memcache_servers,
  71. binary=True, behaviors={"tcp_nodelay": True, "ketama": True})
  72. return self._memcache
  73. class BaseHandler(tornado.web.RequestHandler):
  74. @property
  75. def db(self):
  76. return self.application.db
  77. def get_current_user(self):
  78. user_id = self.get_secure_cookie('user_id')
  79. if not user_id:
  80. return None
  81. user = self.db.users.find_one({'_id': pymongo.objectid.ObjectId(user_id)})
  82. if user is None:
  83. return None
  84. return tornado.web._O(user)
  85. def render_string(self, template_name, **kwargs):
  86. if self.current_user is not None:
  87. tags = self.application.memcache.get('%s/tags' % self.current_user['_id'])
  88. if tags is None:
  89. tags = list(self.db.tags.find({'user': self.current_user['_id']},
  90. sort=[('count', pymongo.DESCENDING)],
  91. limit=20))
  92. self.application.memcache.set('%s/tags' % self.current_user['_id'], tags)
  93. else:
  94. tags = []
  95. return tornado.web.RequestHandler.render_string(
  96. self, template_name, popular_tags=tags,
  97. IS_DEBUG=self.application.config.debug, **kwargs)
  98. class IndexHandler(BaseHandler):
  99. def get(self):
  100. self.render('index.html')
  101. class GoogleAuthHandler(BaseHandler, tornado.auth.GoogleMixin):
  102. @tornado.web.asynchronous
  103. def get(self):
  104. if self.get_argument('openid.mode', None):
  105. self.get_authenticated_user(self.async_callback(self._on_auth))
  106. return
  107. self.authenticate_redirect()
  108. def _on_auth(self, guser):
  109. if not guser:
  110. raise tornado.web.HTTPError(500, "Google auth failed")
  111. user = self.db.users.find_one({'email': guser['email']})
  112. if user is None:
  113. user = {
  114. 'email': guser['email'],
  115. 'name': guser['name'],
  116. }
  117. self.db.users.insert(user)
  118. self.set_secure_cookie('user_id', str(user['_id']))
  119. self.redirect(self.reverse_url('home'))
  120. class LogoutHandler(BaseHandler):
  121. def get(self):
  122. self.clear_cookie('user_id')
  123. self.redirect(self.reverse_url('index'))
  124. class HomeHandler(BaseHandler):
  125. @tornado.web.authenticated
  126. def get(self):
  127. compute_tags(self.db, self.current_user)
  128. query = {'user': self.current_user['_id']}
  129. tag = self.get_argument('tag', None)
  130. if tag is not None:
  131. query['tags'] = tag
  132. bookmarks = self.db.bookmarks.find(
  133. query,
  134. sort=[('modified', pymongo.DESCENDING)],
  135. skip=int(self.get_argument('offset', 0)),
  136. limit=25)
  137. self.render('home.html', bookmarks=(tornado.web._O(b) for b in bookmarks))
  138. class ImportHandler(BaseHandler):
  139. @tornado.web.authenticated
  140. def get(self):
  141. self.render('import.html')
  142. @tornado.web.authenticated
  143. def post(self):
  144. file = self.request.files.get('file')[0]
  145. importer.Importer(self.db, self.current_user, file['body']).import_bookmarks()
  146. self.redirect(self.reverse_url('home'))
  147. class EditBookmarkHandler(BaseHandler):
  148. @tornado.web.authenticated
  149. def get(self, id):
  150. bookmark = self.db.bookmarks.find_one(
  151. dict(user=ObjectId(self.current_user._id), _id=ObjectId(id)))
  152. if bookmark is None:
  153. raise tornado.web.HTTPError(404)
  154. form = forms.BookmarkForm(obj=tornado.web._O(bookmark))
  155. self.render('edit.html', form=form)
  156. @tornado.web.authenticated
  157. def post(self, id):
  158. bookmark = self.db.bookmarks.find_one(
  159. dict(user=ObjectId(self.current_user._id), _id=ObjectId(id)))
  160. if bookmark is None:
  161. raise tornado.web.HTTPError(404)
  162. bookmark = tornado.web._O(bookmark)
  163. form = forms.BookmarkForm(self, obj=bookmark)
  164. if form.validate():
  165. form.populate_obj(bookmark)
  166. self.db.bookmarks.save(bookmark)
  167. self.redirect(self.reverse_url('home'))
  168. else:
  169. self.render('edit.html', form=form)
  170. class NewBookmarkHandler(BaseHandler):
  171. @tornado.web.authenticated
  172. def get(self):
  173. form = forms.BookmarkForm()
  174. self.render('new.html', form=form)
  175. @tornado.web.authenticated
  176. def post(self):
  177. form = forms.BookmarkForm(self)
  178. if form.validate():
  179. bookmark = self.db.bookmarks.find_one({
  180. 'user': self.current_user._id, 'url': form.url.data})
  181. if bookmark is None:
  182. bookmark = dict(user=self.current_user._id,
  183. modified=datetime.datetime.now())
  184. bookmark = tornado.web._O(bookmark)
  185. form.populate_obj(bookmark)
  186. self.db.bookmarks.insert(bookmark)
  187. self.redirect(self.reverse_url('home'))
  188. else:
  189. self.render('new.html', form=form)
  190. class BookmarkletHandler(BaseHandler):
  191. @tornado.web.authenticated
  192. def get(self):
  193. form = forms.BookmarkletForm(self)
  194. if form.validate():
  195. if not form.title.data:
  196. form.title.data = form.url.data
  197. url_digest = util.md5(form.url.data)
  198. bookmark = self.db.bookmarks.find_one({
  199. 'user': self.current_user._id,
  200. 'url_digest': url_digest})
  201. if bookmark is None:
  202. bookmark = {'user': self.current_user._id,
  203. 'modified': datetime.datetime.now()}
  204. bookmark = tornado.web._O(bookmark)
  205. form.populate_obj(bookmark)
  206. self.db.bookmarks.save(bookmark)
  207. self.write('oldu')
  208. else:
  209. self.write('%s' % form.errors)
  210. class TagsHandler(BaseHandler):
  211. @tornado.web.authenticated
  212. def get(self):
  213. tags = self.db.tags.find({'user': self.current_user._id},
  214. sort=[('count', pymongo.DESCENDING)],
  215. limit=20)
  216. self.render('tags.html', tags=(tornado.web._O(tag) for tag in tags))
  217. class DeleteMultipleBookmarksHandler(BaseHandler):
  218. @tornado.web.authenticated
  219. def post(self):
  220. ids = [ObjectId(id) for id in self.get_arguments('ids[]')]
  221. bookmarks = self.db.bookmarks.remove({
  222. 'user': self.current_user._id, '_id': {'$in': ids}})
  223. self.finish()
  224. def compute_tags(db, user):
  225. count = collections.defaultdict(int)
  226. tags = []
  227. for bookmark in db.bookmarks.find({'user': user._id}, fields=['tags']):
  228. if 'tags' not in bookmark:
  229. continue
  230. for tag in bookmark['tags']:
  231. count[tag] += 1
  232. for tag, count in count.items():
  233. tags.append({
  234. "user" : user._id,
  235. 'name': tag,
  236. 'count': count,
  237. })
  238. db.tags.remove({'user': user._id})
  239. if tags:
  240. db.tags.insert(tags)
  241. def main():
  242. tornado.options.parse_command_line()
  243. http_server = tornado.httpserver.HTTPServer(Application())
  244. http_server.listen(options.port)
  245. tornado.ioloop.IOLoop.instance().start()
  246. if __name__ == '__main__':
  247. main()