Guest User

Untitled

a guest
Jun 5th, 2018
288
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.65 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. from google.appengine.ext import webapp
  4. from google.appengine.ext.webapp import util
  5. from google.appengine.ext.webapp import template
  6. from google.appengine.ext.webapp import WSGIApplication
  7.  
  8. from google.appengine.api import memcache
  9.  
  10. from google.appengine.ext import db
  11.  
  12. import logging
  13.  
  14. # ===========================================================================
  15. # MODEL
  16. # ===========================================================================
  17.  
  18. class User(db.Model):
  19. name = db.StringProperty(required=True)
  20. password = db.StringProperty(required=True)
  21. email = db.StringProperty(required=True)
  22. is_admin = db.BooleanProperty(default=False)
  23. is_active = db.BooleanProperty(default=True)
  24.  
  25. @staticmethod
  26. def get_by_uid(user):
  27. query = User.gql('where name = :1', user)
  28. return query.get()
  29.  
  30. @staticmethod
  31. def get_user(user, passwd):
  32. query = User.gql('where name = :1 and password = :2', user, passwd)
  33. return query.get()
  34.  
  35. class Project(db.Model):
  36. name = db.StringProperty(required=True)
  37. description = db.TextProperty(required=True)
  38. date_updated = db.DateTimeProperty(auto_now=True)
  39. date_created = db.DateTimeProperty(auto_now_add=True)
  40. owner = db.ReferenceProperty(User)
  41. tasks = db.IntegerProperty(default=0)
  42. completed_tasks = db.IntegerProperty(default=0)
  43.  
  44. def open_tasks(self):
  45. if self.tasks == 0:
  46. return 0
  47.  
  48. return self.tasks - self.completed_tasks
  49.  
  50. class Task(db.Model):
  51. project = db.ReferenceProperty(Project)
  52. owner = db.ReferenceProperty(User)
  53. summary = db.StringProperty(required=True)
  54. notes = db.TextProperty(required=False)
  55. status = db.StringProperty(
  56. required=True,
  57. default='open',
  58. choices=['open', 'complete']
  59. )
  60. date_updated = db.DateTimeProperty(auto_now=True)
  61. date_created = db.DateTimeProperty(auto_now_add=True)
  62.  
  63. @staticmethod
  64. def status_list():
  65. return ['open', 'complete']
  66.  
  67. # ===========================================================================
  68.  
  69. class RootHandler(webapp.RequestHandler):
  70. # This class wraps the standard RequestHandler so that we can
  71. # add some handy methods likely to be needed by a lot of other
  72. # handlers, such as session management and "canned" responses.
  73.  
  74. # The session id (really the user's identifier) is stored
  75. # in a cookie.
  76.  
  77. cookie_name = 'taskbook'
  78.  
  79. def get_attr(self, name):
  80. # Return a utf-8 encoded request attribute value,
  81. # or an empty string if it's not present.
  82.  
  83. value = self.request.get(name)
  84.  
  85. if value is None:
  86. return unicode('', 'utf-8')
  87.  
  88. # Return the value properly utf-8 encoded.
  89.  
  90. try:
  91. return unicode(value.strip(), 'utf-8')
  92.  
  93. # If the value is already encoded, just return it.
  94. except TypeError, e:
  95. return value.strip()
  96.  
  97. def get_session_id(self):
  98. # Return the ID stored in the session cookie, or None
  99. # if it's not found.
  100.  
  101. self.request.charset = None
  102.  
  103. try:
  104. return self.request.cookies[self.cookie_name]
  105.  
  106. except KeyError:
  107. return None
  108.  
  109. def get_session_user(self):
  110. # Return the user object for the currently logged in user
  111. # or raise an exception if the user's not logged in.
  112.  
  113. uid = self.is_logged_in()
  114. if not uid:
  115. raise Exception, "Can't get user."
  116.  
  117. return User.get_by_uid(uid)
  118.  
  119. def is_logged_in(self):
  120. # Tests whether or not there's an identifiable
  121. # session id.
  122.  
  123. return self.get_session_id()
  124.  
  125. def sign_in(self, id):
  126. # Establishes a session (kind of) by setting a cookie
  127. # we can expect to see back on the next request.
  128.  
  129. cookie_str = "%s=%s;path=/;" % (self.cookie_name, id)
  130. self.response.headers['set-cookie'] = cookie_str
  131.  
  132. def sign_out(self):
  133. # Removes the session cookie so that the next request from
  134. # the browser will seem like a branch new request.
  135.  
  136. the_past = 'Fri, 31-Dec-2000 23:59:59 GMT'
  137. cookie_str = '%s=;expires=%s;path=/' % (self.cookie_name, the_past)
  138. self.response.headers['set-cookie'] = cookie_str
  139.  
  140. def url_param(self, pattern):
  141. # Given a pattern like /project/${id} and a path like /project/2,
  142. # this method will return "2". In other words, its a way to extract
  143. # REST style parameters from a URL path. Returns None if something
  144. # goes wrong.
  145.  
  146. pt = pattern.split('/')
  147. pa = self.request.path.split('/')
  148.  
  149. for x in range(0,len(pt)):
  150. if pt[x].startswith('$'):
  151. try:
  152. return pa[x]
  153. except IndexError:
  154. return none
  155.  
  156.  
  157. # ===========================================================================
  158.  
  159. def login_required(fn):
  160. # Decorate handler methods with this if you want users to be
  161. # redirected to a login page if they're not logged in, then returned
  162. # to the page they requested when they've successfully logged in.
  163.  
  164. def new_fn(handler, *args, **kwargs):
  165. uid = handler.is_logged_in()
  166. if not uid:
  167. data = { 'redirect_to' : handler.request.path }
  168. page = template.render('www/html/login.html', data)
  169. handler.response.out.write(page)
  170. else:
  171. return fn(handler, *args, **kwargs)
  172.  
  173. return new_fn
  174.  
  175. def user_required(fn):
  176. # Makes sure that the user has logged in before invoking the decorated
  177. # handler, or returns a 401 response. This should decorate handlers
  178. # which are APIs (for Ajax calls, say) rather than handlers tasked
  179. # with dispatching pages.
  180.  
  181. def new_fn(handler, *args, **kwargs):
  182. uid = handler.is_logged_in()
  183. if not uid:
  184. handler.response.set_status(401)
  185. return
  186. else:
  187. return fn(handler, *args, **kwargs)
  188.  
  189. return new_fn
  190.  
  191. # ===========================================================================
  192.  
  193. # Handlers meant to service Ajax API calls rather than to render pages
  194. # of some sort.
  195.  
  196. class SignInApiHandler(RootHandler):
  197.  
  198. def post(self):
  199. name = self.get_attr('name')
  200. passwd = self.get_attr('password')
  201.  
  202. user = User.get_user(name, passwd)
  203.  
  204. if user:
  205. logging.info('log in successful')
  206. self.sign_in(name)
  207. self.response.set_status(200)
  208. else:
  209. logging.info('log in unsuccessful')
  210. self.response.set_status(401)
  211.  
  212. class TaskSummaryApiHandler(RootHandler):
  213.  
  214. @user_required
  215. def post(self):
  216. try:
  217. task_id = self.url_param('/api/task/${id}/summary')
  218. summary = self.get_attr('summary')
  219.  
  220. task = Task.get_by_id(int(task_id))
  221.  
  222. user = self.get_session_user()
  223. if user.key() != task.owner.key():
  224. logging.error('%s tried to update task note on task owned by %s'
  225. % (user.name, task.owner.name))
  226. self.response.set_status(401)
  227. return
  228.  
  229. task.summary = summary
  230. task.put()
  231.  
  232. self.response.set_status(200)
  233. return
  234.  
  235. except Exception, e:
  236. logging.error("problem updating task summary: %s" % e)
  237. self.response.set_status(500)
  238. return
  239.  
  240.  
  241. class TaskNoteApiHandler(RootHandler):
  242.  
  243. @user_required
  244. def post(self):
  245.  
  246. try:
  247. task_id = self.url_param('/api/task/${id}/note')
  248. new_note = self.get_attr('note')
  249.  
  250.  
  251. task = Task.get_by_id(int(task_id))
  252.  
  253. user = self.get_session_user()
  254. if user.key() != task.owner.key():
  255. logging.error('%s tried to update task note on task owned by %s'
  256. % (user.name, task.owner.name))
  257. self.response.set_status(401)
  258. return
  259.  
  260. task.notes = new_note
  261. task.put()
  262.  
  263. self.response.set_status(200)
  264. return
  265.  
  266. except Exception, e:
  267. logging.error("problem updating task note: %s" % e)
  268. self.response.set_status(500)
  269. return
  270.  
  271. class TaskStatusApiHandler(RootHandler):
  272.  
  273. @user_required
  274. def post(self):
  275.  
  276. try:
  277.  
  278. task_id = self.url_param('/api/task/${id}/status')
  279. new_status = self.get_attr('status')
  280.  
  281. task = Task.get_by_id(int(task_id))
  282.  
  283. user = self.get_session_user()
  284. if user.key() != task.owner.key():
  285. logging.error('%s tried to update task status on task owned by %s'
  286. % (user.name, task.owner.name))
  287. self.response.set_status(401)
  288. return
  289.  
  290. task.status = new_status
  291. task.put()
  292.  
  293. project = task.project
  294. if new_status == 'open':
  295. project.completed_tasks = project.completed_tasks - 1
  296. else:
  297. project.completed_tasks = project.completed_tasks + 1
  298.  
  299. project.put()
  300.  
  301. self.response.set_status(200)
  302. return
  303.  
  304. except Exception, e:
  305. logging.error("problem updating task status: %s" % e)
  306. self.response.set_status(500)
  307. return
  308.  
  309. class CreateTaskApiHandler(RootHandler):
  310.  
  311. @user_required
  312. def post(self):
  313.  
  314. try:
  315. uid = self.is_logged_in()
  316.  
  317. project_id = self.get_attr('project-id')
  318. summary = self.get_attr('summary')
  319. notes = self.get_attr('notes')
  320.  
  321. user = User.get_by_uid(uid)
  322. project = Project.get_by_id(int(project_id))
  323.  
  324. if project.owner.key() != user.key():
  325. # Note: this means an admin can't create a task on someone
  326. # else's project either. I'm okay with that.
  327.  
  328. logging.error('%s tried to create a task on project owned by %s'
  329. % (user.name, project.owner.name))
  330. self.response.set_status(401)
  331. return
  332.  
  333. task = Task(
  334. owner=user,
  335. summary=summary,
  336. project=project,
  337. notes=notes
  338. )
  339.  
  340. task.put()
  341.  
  342. project.tasks = project.tasks + 1
  343. project.put()
  344.  
  345. self.response.set_status(200)
  346. return
  347.  
  348. except Exception, e:
  349. logging.error("problem creating task: %s" % e)
  350. self.response.set_status(500)
  351. return
  352.  
  353. class CreateProjectApiHandler(RootHandler):
  354.  
  355. # TODO: Make sure that the project name is not already used for a given
  356. # owner.
  357. # TODO: Figure out a way to return an actual error message. Header?
  358.  
  359. @user_required
  360. def post(self):
  361.  
  362. try:
  363. uid = self.is_logged_in()
  364. user = User.get_by_uid(uid)
  365.  
  366. name = self.get_attr('name')
  367. description = self.get_attr('description')
  368.  
  369. project = Project(name=name, description=description, owner=user)
  370. key = project.put()
  371. self.response.set_status(200)
  372. return
  373.  
  374. except Exception, e:
  375. logging.error("problem creating project: %s" % e)
  376. self.response.set_status(500)
  377. return
  378.  
  379.  
  380. # ===========================================================================
  381.  
  382. # Handlers for rendering pages
  383.  
  384. class DashboardPageHandler(RootHandler):
  385.  
  386. @login_required
  387. def get(self):
  388.  
  389. user = self.get_session_user()
  390.  
  391. projects = Project.all()
  392. projects.order('date_created')
  393.  
  394. if not user.is_admin:
  395. projects.filter('owner =', user)
  396.  
  397. if projects.count() == 0:
  398. projects = []
  399.  
  400. data = {
  401. 'user' : user,
  402. 'projects' : projects
  403. }
  404.  
  405. page = template.render('www/html/dashboard.html', data)
  406. self.response.out.write(page)
  407.  
  408.  
  409. class ProjectPageHandler(RootHandler):
  410.  
  411. @login_required
  412. def get(self):
  413.  
  414. # get the user
  415.  
  416. user = self.get_session_user()
  417.  
  418. # get the project
  419.  
  420. p_id = self.url_param('/project/${id}')
  421.  
  422. if p_id is None:
  423. self.response.set_status(404)
  424. return
  425.  
  426. project = Project.get_by_id(int(p_id))
  427.  
  428. if project is None:
  429. self.response.set_status(404)
  430. return
  431.  
  432. if not user.is_admin and project.owner.key() != user.key():
  433. self.response.set_status(401)
  434. return
  435.  
  436. # get the tasks
  437.  
  438. tasks = Task.all()
  439. tasks.filter('project = ', project)
  440. tasks.order('date_created')
  441.  
  442. if tasks.count() == 0:
  443. tasks = []
  444.  
  445. # render the template
  446.  
  447. data = { 'user' : user, 'project' : project, 'tasks' : tasks }
  448. page = template.render('www/html/project.html', data)
  449. self.response.out.write(page)
  450.  
  451. class AboutHandler(RootHandler):
  452.  
  453. @login_required
  454. def get(self):
  455. user = self.get_session_user()
  456. data = { 'user' : user }
  457. page = template.render('www/html/about.html', data)
  458. self.response.out.write(page)
  459.  
  460. class LogoutHandler(RootHandler):
  461.  
  462. def get(self):
  463. self.sign_out()
  464. self.redirect('/')
  465.  
  466. class LoginHandler(RootHandler):
  467.  
  468. def get(self):
  469. if self.is_logged_in():
  470. self.redirect('/dashboard')
  471. return
  472.  
  473. data = {
  474. 'redirect_to' : '/dashboard'
  475. }
  476. page = template.render('www/html/login.html', data)
  477. self.response.out.write(page)
  478.  
  479. # ===========================================================================
  480. # MAIN
  481. # ===========================================================================
  482.  
  483. def init_user(name, passwd, email, admin=False):
  484. user = User.get_user(name, passwd)
  485. if user is None:
  486. user = User(name=name, password=passwd, email=email)
  487. user.is_admin = admin
  488. user.put()
  489.  
  490. def init_users():
  491. init_user('admin', '-----', 'keith.irwin@gmail.com', admin=True)
  492. init_user('keith', '-----', 'keith.irwin@gmail.com', admin=False)
  493. init_user('demo', 'demo', 'keith.irwin@gmail.com', admin=False)
  494.  
  495. def main():
  496. handlers = [
  497.  
  498. # callbacks for clients
  499.  
  500. ('/api/sign-in', SignInApiHandler),
  501. ('/api/project', CreateProjectApiHandler),
  502. ('/api/task', CreateTaskApiHandler),
  503. ('/api/task/.*/status', TaskStatusApiHandler),
  504. ('/api/task/.*/note', TaskNoteApiHandler),
  505. ('/api/task/.*/summary', TaskSummaryApiHandler),
  506.  
  507. # actual rendered pages
  508.  
  509. ('/logout', LogoutHandler),
  510. ('/login', LoginHandler),
  511. ('/about', AboutHandler),
  512. ('/dashboard', DashboardPageHandler),
  513. ('/project/.*', ProjectPageHandler),
  514.  
  515. # If all else fails, go to the log in page. TODO: dispatch
  516. # to some friendly version of a 404 page letting the user know
  517. # why it is they're not seeing something they expect to see.
  518.  
  519. ('/.*', LoginHandler)
  520. ]
  521. application = webapp.WSGIApplication(handlers, debug=True)
  522. util.run_wsgi_app(application)
  523.  
  524.  
  525. if __name__ == '__main__':
  526. init_users()
  527. main()
Add Comment
Please, Sign In to add comment