PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/demisauce/demisauce/controllers/api.py

https://github.com/araddon/demisauce
Python | 1040 lines | 1027 code | 6 blank | 7 comment | 21 complexity | 4aa8a3d5566031d6194dce2673ed5c83 MD5 | raw file
  1. import logging, urllib, json, hashlib, re
  2. import tornado.escape
  3. from tornado.options import options
  4. from sqlalchemy.sql import and_
  5. from sqlalchemy.orm import eagerload
  6. from demisauce.controllers import BaseHandler, requires_authn
  7. from demisauce import model
  8. from demisauce.model import meta
  9. from demisauce.model.site import Site
  10. from demisauce.model.template import Template
  11. from demisauce.model.user import Person, Group
  12. from demisauce.model.activity import Activity
  13. from demisauce.model.service import Service, App
  14. from demisauce.model.object import Object
  15. from demisauce.model import actions
  16. from gearman import GearmanClient
  17. from gearman.task import Task
  18. from demisaucepy import cache, cache_setup
  19. from demisaucepy.serializer import is_json
  20. log = logging.getLogger("demisauce")
  21. CACHE_DURATION = 600 if not options.debug else 10
  22. mailsrch = re.compile(r'[\w\-][\w\-\.]+@[\w\-][\w\-\.]+[a-zA-Z]{1,4}')
  23. def requires_api(method):
  24. """Decorate methods with this to require that the user be logged in
  25. and that they be an admin"""
  26. @functools.wraps(method)
  27. def wrapper(self, *args, **kwargs):
  28. if not self.current_user or not self.current_user.is_authenticated:
  29. if self.request.method == "GET":
  30. url = self.get_login_url()
  31. if "?" not in url:
  32. url += "?" + urllib.urlencode(dict(next=self.request.uri))
  33. self.redirect(url)
  34. return
  35. raise tornado.web.HTTPError(403)
  36. elif not self.current_user.is_sysadmin:
  37. if self.request.method == "GET":
  38. self.redirect('/?msg=Not+Authorized')
  39. return
  40. raise tornado.web.HTTPError(403)
  41. return method(self, *args, **kwargs)
  42. return wrapper
  43. def requires_site(target):
  44. """
  45. A decorator to protect the API methods to be able to
  46. accept api by either apikey or logged on user, in future oath?
  47. """
  48. def decorator(self,*args,**kwargs):
  49. site = self.get_current_site()
  50. if not self.site:
  51. log.info('403, api call ' )
  52. return self.nonresponse(403)
  53. else:
  54. return target(self,*args,**kwargs)
  55. return decorator
  56. def requires_site_slug(target):
  57. """allows access with only a slug which identifies a site
  58. but isn't really secure """
  59. def decorator(self,*args):
  60. site = get_current_site()
  61. if not site:
  62. log.info('403, api call ' )
  63. return self.nokey()
  64. else:
  65. return target(self,*args)
  66. return decorator
  67. class ApiBaseHandler(BaseHandler):
  68. def __init__(self, application, request, transforms=None):
  69. super(ApiBaseHandler, self).__init__(application, request, transforms=transforms)
  70. self.set_status(200)
  71. self._queue_actions = {}
  72. def cache_url(self):
  73. url = self.request.full_url()
  74. url = url.replace('&cache=false',"").replace("cache=false","")
  75. return url
  76. def nokey(self):
  77. """
  78. returns error code for no api key
  79. """
  80. response.headers['Content-Type'] = 'application/xhtml+xml'
  81. return "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + \
  82. "<exception id=\"401\" >Invalid API Key</exception>"
  83. def args_todict(self):
  84. d = {}
  85. for k in self.request.arguments.keys():
  86. d[k] = self.get_argument(k)
  87. for k in ['cache','apikey']:
  88. if self.request.query and k in d and "%s=" % k in self.request.query:
  89. d.pop(k)
  90. return d
  91. def is_json_post(self):
  92. 'Determines if json post?'
  93. return is_json(self.request.body)
  94. def normalize_args(self,noun=None,requestid=None,action=None,format="json"):
  95. "Normalizes and formats arguments"
  96. self.format = format if format is not None else "json"
  97. self.format = self.format.replace('.','')
  98. self.noun = noun
  99. self.qry = []
  100. self.object = None
  101. self._result = None
  102. self._queue_actions = {}
  103. #log.debug("in normalize: noun=%s, requestid=%s, action=%s" % (noun,requestid,action))
  104. if requestid is None or requestid in ['',None,'all','list']:
  105. # /api/noun.format
  106. # /api/noun/list.format
  107. # /api/noun/all.format
  108. # /api/noun/all/selector(action).format
  109. # /api/noun/list/selector(action).format
  110. self.id = 'list'
  111. #action = 'list'
  112. if action == None or action == '.json':
  113. action = 'list'
  114. else:
  115. # /api/noun/id/action.format?apikey=key&limit=100&start=101
  116. # /api/noun/id/selector(action).format
  117. # /api/noun/id.format
  118. # /api/noun/siteslug?more
  119. if requestid and hasattr(requestid,'isdigit') and \
  120. (type(requestid) == int or requestid.isdigit()):
  121. self.id = int(requestid)
  122. else:
  123. self.id = requestid
  124. if action == None:
  125. action = 'get'
  126. self.action = action
  127. # find query, start, limi
  128. self.q = self.get_argument("q",None)
  129. self.limit = self.get_argument("limit",'100')
  130. if not self.limit.isdigit():
  131. self.limit = 100
  132. else:
  133. self.limit = int(self.limit)
  134. self.limit = 1000 if self.limit > 1000 else self.limit
  135. self.start = self.get_argument("start",'0')
  136. if not self.start.isdigit():
  137. self.start = 0
  138. else:
  139. self.start = int(self.start)
  140. def json(self):
  141. if self.qry and self.qry != []:
  142. json_data = []
  143. for row in self.qry:
  144. data = self.json_formatter(row)
  145. if isinstance(data,(list)):
  146. json_data = json_data + data
  147. else:
  148. # normally it is a dict
  149. json_data.append(data)
  150. return json.dumps(json_data)
  151. else:
  152. return None
  153. def json_formatter(self,o):
  154. """default json formatter, uses SerializationMixin """
  155. if o:
  156. return o.to_dict()
  157. return ''
  158. def object_load_dict(self,o,data_dict):
  159. 'http post handle args'
  160. return data_dict
  161. def after_dict_load(self,o):
  162. 'handle post dict load cleanup'
  163. pass
  164. def after_save(self,data_dict):
  165. 'after save of object'
  166. pass
  167. def _add_object(self,data_dict):
  168. o = None
  169. if self.id not in ['',None,'list','get','all']:
  170. self.action_get_object(self.id, data_dict)
  171. o = self.object
  172. if not o:
  173. o = self.__class__.object_cls()
  174. o.site_id = self.site.id
  175. if len(self.qry) == 0:
  176. self.qry.append(o)
  177. log.debug("about to update %s" % data_dict)
  178. data_dict = self.object_load_dict(o,data_dict)
  179. o.from_dict(data_dict,allowed_keys=self.__class__.object_cls._allowed_api_keys)
  180. self.after_dict_load(self.object)
  181. o.after_load()
  182. if o and o.isvalid():
  183. self.object = o
  184. o.save()
  185. cache.cache.delete(cache.cache_key(self.cache_url()))
  186. self.after_save(data_dict)
  187. self.set_status(201)
  188. else:
  189. log.error("what went wrong, not valid %s" % o)
  190. def handle_post(self):
  191. log.debug("in handle post, args= %s, body=%s" % (self.request.arguments,self.request.body))
  192. if self.is_json_post():
  193. #log.debug("yes, json post")
  194. pyvals = json.loads(self.request.body)
  195. if pyvals and isinstance(pyvals,dict):
  196. self._add_object(pyvals)
  197. elif pyvals and isinstance(pyvals,list):
  198. for json_data in pyvals:
  199. self._add_object(json_data)
  200. else:
  201. self._add_object(self.args_todict())
  202. def action_get_object(self,id, data_dict = {}):
  203. if self.id > 0:
  204. self.object = self.__class__.object_cls.saget(id)
  205. if self.object:
  206. self.qry = [self.object]
  207. else:
  208. self.qry = []
  209. def action_get_list(self,q=None):
  210. if not q:
  211. log.debug("In list.q limit = %s" % self.limit)
  212. qry = self.db.session.query(self.__class__.object_cls).limit(self.limit)
  213. if self.start > 0:
  214. log.debug("self.start = %s" % self.start)
  215. qry = qry.offset(self.start)
  216. else:
  217. qry = self.db.session.query(self.__class__.object_cls).filter(
  218. self.__class__.object_cls.name.like('%' + q + '%'))
  219. self.qry = qry
  220. def do_delete(self):
  221. if self.id and self.id not in ['list','all']:
  222. self.action_get_object(self.id)
  223. if self.object and hasattr(self.object,'delete'):
  224. url = self.cache_url()
  225. log.info("DELETE id=%s for url=%s" % (self.id,url))
  226. self.object.delete()
  227. self.object = None
  228. self.qry = None
  229. self.set_status(204)
  230. cache.cache.delete(cache.cache_key(url))
  231. result = cache.cache.get(cache.cache_key(url))
  232. else:
  233. log.error("not found? %s, %s" % (self.noun, self.id))
  234. def nonresponse(self,status_code):
  235. self.set_status(status_code)
  236. self.write("{'status':'failure'}")
  237. return
  238. def do_get(self):
  239. if self._has_cache():
  240. return
  241. if self.id and self.id == 'list' and self.action == 'list':
  242. self.action_get_list(q=self.q)
  243. elif self.id and self.action == 'get':
  244. self.action_get_object(self.id)
  245. elif hasattr(self,self.action):
  246. getattr(self,self.action)()
  247. else:
  248. self.action_get_object(self.id)
  249. if self.object is None and self.qry == [] and self._status_code == 200:
  250. log.debug("setting status = 404")
  251. self.set_status(404)
  252. def do_post(self):
  253. if hasattr(self,"%s_POST" % self.action):
  254. getattr(self,"%s_POST" % self.action)()
  255. elif self.action not in ['list','get','post','all','json','delete'] and \
  256. hasattr(self,self.action):
  257. log.debug("POST %s" % self.action)
  258. getattr(self,self.action)()
  259. else:
  260. self.handle_post()
  261. def render_json(self,jsonstring):
  262. self.set_header("Content-Type", "application/json")
  263. if 'jsoncallback' in self.request.arguments:
  264. self.write('%s(%s)' % (self.get_argument('jsoncallback'),jsonstring))
  265. elif 'callback' in self.request.arguments:
  266. self.write('%s(%s)' % (self.get_argument('callback'),jsonstring))
  267. else:
  268. self.write('%s' % (jsonstring))
  269. def _has_cache(self):
  270. if not 'cache' in self.request.arguments:
  271. url = self.request.full_url()
  272. result = cache.cache.get(cache.cache_key(url))
  273. log.debug(cache.cache_key(url))
  274. if result:
  275. log.debug("found cache for url=%s" % url)
  276. self._result = result
  277. return True
  278. return False
  279. def do_cache(self,result_string,duration=CACHE_DURATION):
  280. url = self.cache_url()
  281. #log.debug('saving cache url=%s = %s' % (url,result_string))
  282. cache.cache.set(cache.cache_key(url),result_string,duration)
  283. def render_to_format(self):
  284. _stringout = None
  285. # ===== Which data to show, prep formatting
  286. if self.format == 'json' and not self._result:
  287. if self.action in ['addupdate','delete']:
  288. _stringout = '{"msg":"success"}'
  289. elif self.action == 'list' or self.action == 'get':
  290. _stringout = self.json()
  291. elif hasattr(self,self.action):
  292. _stringout = self.json()
  293. else:
  294. _stringout = self.json()
  295. # objectid
  296. if self.object:
  297. self.set_header("X-Demisauce-ID", str(self.object.id))
  298. self.set_header("X-Demisauce-SITEID", str(self.site.id))
  299. # ==== Serialization Format
  300. if self.format == 'json':
  301. if _stringout:
  302. self.render_json(_stringout)
  303. if self.request.method in('GET','POST'):
  304. self.do_cache(_stringout)
  305. elif self._result:
  306. self.render_json(self._result)
  307. elif self.format == 'xml':
  308. self.set_header("Content-Type", 'application/xhtml+xml')
  309. def get(self,noun=None,requestid='all',action=None,format="json"):
  310. self.normalize_args(noun, requestid,action,format)
  311. log.debug("GET API: noun=%s, id=%s, action=%s, format=%s, url=%s, start=%s, limit=%s, args=%s" % (self.noun,self.id,self.action,self.format,self.request.path,self.start,self.limit,str(self.request.arguments)))
  312. self.do_get()
  313. #log.debug("in get status = %s" % (self._status_code))
  314. self.render_to_format()
  315. def post(self,noun=None,requestid='all',action=None,format="json"):
  316. self.normalize_args(noun, requestid,action,format)
  317. log.debug("POST API: noun=%s, id=%s, action=%s, format=%s, url=%s, start=%s, limit=%s" % (self.noun,self.id,self.action,self.format,self.request.path,self.start,self.limit))
  318. self.do_post()
  319. self.render_to_format()
  320. def put(self,noun=None,requestid='all',action=None,format="json"):
  321. self.normalize_args(noun, requestid,action,format)
  322. log.debug("PUT API: noun=%s, id=%s, action=%s, format=%s, url=%s, start=%s, limit=%s" % (self.noun,self.id,self.action,self.format,self.request.path,self.start,self.limit))
  323. self.do_post()
  324. self.render_to_format()
  325. def delete(self,noun=None,requestid='all',action=None,format="json"):
  326. self.normalize_args(noun, requestid,action,format)
  327. self.do_delete()
  328. self.render_to_format()
  329. def _after_finish(self):
  330. 'override this for work post'
  331. q = self._queue_actions
  332. for key in q.keys():
  333. d = q[key]
  334. d.update({'site_id':self.site.id})
  335. actions.do_action(key,**d)
  336. log.debug("in after finish key=%s, val=%s" % (key,d))
  337. super(ApiBaseHandler, self)._after_finish()
  338. class ApiSecureHandler(ApiBaseHandler):
  339. @requires_site
  340. def get(self,noun=None,requestid='all', action=None,format="json"):
  341. super(ApiSecureHandler,self).get(noun,requestid,action,format)
  342. @requires_site
  343. def post(self,noun=None,requestid="all", action=None,format="json"):
  344. super(ApiSecureHandler,self).post(noun,requestid,action,format)
  345. class ActivityApiHandler(ApiBaseHandler):
  346. object_cls = Activity
  347. def action_get_object(self,id, data_dict = {}):
  348. self.object = None
  349. self.qry = []
  350. def get(self,site_slug='',format="json"):
  351. if not self.user and 'hashedemail' in self.request.arguments:
  352. user = user.Person.by_hashedemail(str(self.get_argument('hashedemail')))
  353. elif self.user:
  354. user = self.user
  355. else:
  356. return
  357. if 'site_slug' in self.request.arguments:
  358. site_slug = str(self.get_argument('site_slug'))
  359. if 'activity' in self.request.arguments:
  360. activity_name = str(self.get_argument('activity'))
  361. a = Activity(site_id=user.site_id,person_id=user.id,activity=activity_name)
  362. a.ip = self.request.remote_ip
  363. if 'ref_url' in self.request.arguments:
  364. a.ref_url = self.get_argument('ref_url')
  365. if 'category' in self.request.arguments:
  366. a.category = self.get_argument('category')
  367. if 'cnames' in self.request.arguments:
  368. names = [n for n in self.get_argument('cnames').split(',') if n != '']
  369. if len(names) > 0:
  370. a.custom1name = names[0]
  371. a.custom1val = request.params[names[0]]
  372. if len(names) > 1:
  373. a.custom2name = names[1]
  374. a.custom2val = request.params[names[1]]
  375. a.save()
  376. @requires_site
  377. def post(self,site_slug='',format="json"):
  378. requestid = site_slug
  379. super(ActivityApiHandler,self).post('activity',requestid,'addupdate',format=format)
  380. def options(self,site_slug='',format='json'):
  381. log.debug("in activity api OPTIONS")
  382. class TemplateApiHandler(ApiSecureHandler):
  383. object_cls = Template
  384. def action_get_object(self,id, data_dict = {}):
  385. if type(id) == int and id > 0:
  386. self.object = Template.get(site_id=self.site.id,id=id)
  387. elif id == 0 and data_dict.has_key('slug'):
  388. log.debug("they asked for id = 0, lets ignore and doublecheck slug = %s" % data_dict['slug'])
  389. self.object = Template.by_slug(site_id=self.site.id,slug=data_dict['slug'])
  390. if self.object:
  391. log.debug("found object, sweet! %s" % self.object.id)
  392. else:
  393. log.debug("trying to get by slug %s" % (id))
  394. self.object = Template.by_slug(site_id=self.site.id,slug=id)
  395. if self.object:
  396. self.qry = [self.object]
  397. else:
  398. self.set_status(404)
  399. log.error("no email %s, status=%s" % (self.id, self._status_code))
  400. def send(self):
  401. log.error("in send of email api")
  402. emailjson = json.loads(self.request.body)
  403. if emailjson and 'template_name' in emailjson:
  404. log.error("weehah, body json = %s" % emailjson)
  405. #TODO: revamp and use self.db.gearman_client
  406. gearman_client = GearmanClient(options.gearman_servers)
  407. gearman_client.do_task(Task("email_send",self.request.body, background=True))
  408. def json_formatter(self,o):
  409. if o:
  410. keys=['slug','name','subject','from_email',
  411. 'reply_to','id','from_name','template','template_html','to']
  412. output = o.to_dict(keys=keys)
  413. return output
  414. return None
  415. def action_get_list(self,q=None):
  416. if q:
  417. qry = self.db.session.query(Template).filter(and_(
  418. Template.name.like('%' + q + '%'),Template.site_id==self.site.id))
  419. else:
  420. qry = Template.all(site_id=self.site.id)
  421. log.debug("in email list, qry = %s" % qry)
  422. self.qry = qry
  423. class PersonAnonApi(ApiBaseHandler):
  424. object_cls = Person
  425. def init_user(self):
  426. # Need site?
  427. ds_id = self.id
  428. site_key = self.db.cache.get(str(ds_id))
  429. log.debug("init_user ds_id,site_key = %s = %s" % (ds_id,site_key))
  430. if site_key:
  431. site = Site.by_apikey(str(site_key))
  432. if site:
  433. user = Person.get(site.id,ds_id)
  434. if not user:
  435. log.error("user not found? id = %s" % ds_id)
  436. else:
  437. log.error("no site? %s" % ds_id)
  438. self.set_current_user(user,is_authenticated = True)
  439. log.debug("tried to init_user succeeded")
  440. self.set_status(204) # succuess, no content
  441. else:
  442. log.error("tried to init_user failed ds_id = %s, url=%s" % (ds_id,self.request.full_url()))
  443. self.set_status(400)
  444. self.write("{'status':'failure'}")
  445. class PersonApiHandler(ApiSecureHandler):
  446. object_cls = Person
  447. def after_save(self,data_dict):
  448. if hasattr(self.object,'is_new') and self.object.is_new:
  449. self._queue_actions.update({'new_user':{'user_id':self.object.id}})
  450. def object_load_dict(self,o,data_dict):
  451. 'http post handle args'
  452. if 'attributes' in data_dict:
  453. attr_list = []
  454. if isinstance(data_dict['attributes'],list):
  455. attr_list = data_dict['attributes']
  456. elif isinstance(data_dict['attributes'],dict):
  457. attr_list = [data_dict['attributes']]
  458. for attr in attr_list:
  459. object_type = "attribute" if not 'object_type' in attr else attr['object_type']
  460. category = "attribute" if not 'category' in attr else attr['category']
  461. encoding = 'str' if not 'encoding' in attr else attr['encoding']
  462. o.add_attribute(attr['name'],attr['value'],object_type=object_type,
  463. encoding=encoding,category=category)
  464. data_dict.pop('attributes')
  465. if 'delete_attributes' in data_dict:
  466. for attr in data_dict['delete_attributes']:
  467. ua = o.get_attribute(attr['name'])
  468. log.debug('deletting attribute = id=%s, name=%s' % (ua.id, ua.name))
  469. ua.delete()
  470. data_dict.pop('delete_attributes')
  471. return data_dict
  472. def change_email(self):
  473. self.handle_post()
  474. def delete_by_get(self):
  475. self.action_get_object(self.id)
  476. if self.object and hasattr(self.object,'delete'):
  477. url = self.request.full_url()
  478. log.debug("DELETE id=%s for url=%s" % (self.id,url))
  479. self.object.delete()
  480. cache.cache.delete(cache.cache_key(url))
  481. self.object = None
  482. self.qry = []
  483. self.set_status(204)
  484. else:
  485. log.error("not found? %s, %s" % (self.noun, self.id))
  486. def action_get_object(self,id, data_dict = {}):
  487. if isinstance(self.id,int) and self.id > 0:
  488. #meta.DBSession.close()
  489. #meta.DBSession()
  490. self.object = Person.get(self.site.id,self.id)
  491. if not self.object:
  492. # ??
  493. self.object = Person.by_foreignid(self.site.id,self.id)
  494. elif isinstance(self.id,int) and self.id == 0 and 'email' in data_dict:
  495. self.object = Person.by_email(self.site.id,data_dict['email'].lower())
  496. elif 'foreign_id' in data_dict:
  497. self.object = Person.by_foreignid(self.site.id,data_dict['foreign_id'])
  498. else:
  499. id = urllib.unquote_plus(self.id)
  500. if mailsrch.match(id):
  501. self.object = Person.by_email(self.site.id,id)
  502. else:
  503. self.object = Person.by_hashedemail(self.site.id,id)
  504. log.debug('getting by hashed/email: %s, %s' % (id, self.object))
  505. if self.object:
  506. self.qry = [self.object]
  507. def json_formatter(self,o):
  508. if o:
  509. return o.to_dict_api()
  510. return None
  511. def action_get_list(self,q=None):
  512. if q:
  513. qry = self.db.session.query(Person).filter(and_(
  514. Person.name.like('%' + q + '%'),Person.is_anonymous==1))
  515. else:
  516. qry = self.db.session.query(Person).filter(and_(Person.site_id==self.site.id))
  517. self.qry = qry
  518. def pre_init_user(self):
  519. """A push to pre-initiate session (step 1) optional
  520. body of json to add/update user"""
  521. user = None
  522. user_dict = {}
  523. if self.request.method == 'POST':
  524. if self.is_json_post():
  525. user_dict = json.loads(self.request.body)
  526. else:
  527. user_dict = self.args_todict()
  528. log.debug("Loaded data user_dict=%s" % user_dict)
  529. self.action_get_object(self.id, user_dict)
  530. if not self.object:
  531. log.debug("creating new user, not found %s dict=%s" % (self.id,user_dict))
  532. self.object = Person(site_id=self.site.id,foreign_id=self.id)
  533. if self.object:
  534. self.object.from_dict(user_dict,allowed_keys=Person._allowed_api_keys)
  535. self.object.save()
  536. if hasattr(self.object,'is_new') and self.object.is_new:
  537. self._queue_actions.update({'new_user':{'user_id':self.object.id}})
  538. else:
  539. self.action_get_object(self.id)
  540. if self.object:
  541. user_key = self.object.id
  542. log.debug("setting cache = %s, %s" % (str(user_key),self.site.key))
  543. self.db.cache.set(str(user_key),self.site.key,120) # 2 minutes only
  544. site_key = self.db.cache.get(str(user_key))
  545. log.debug("site_key, self.site.key = %s, %s" % (site_key, self.site.key))
  546. assert site_key == self.site.key
  547. self.qry = [self.object]
  548. def options(self,site_slug='',format='json'):
  549. log.debug("in person api OPTIONS")
  550. class ServiceApiHandler(ApiSecureHandler):
  551. object_cls = Service
  552. def action_get_object(self,id, data_dict = {}):
  553. if isinstance(id,int) and self.id > 0:
  554. self.object = Service.saget(id)
  555. elif isinstance(id,int) and self.id == 0:
  556. if 'key' in data_dict:
  557. self.object = Service.by_app_service(data_dict['key'])
  558. else:
  559. self.object = Service.by_app_service(self.id)
  560. if self.object:
  561. self.qry = [self.object]
  562. else:
  563. self.set_status(404)
  564. log.error("no service %s for %s" % (self.id,self.request.full_url()))
  565. def json_formatter(self,o):
  566. if o:
  567. keys=['key','name','format','url',
  568. 'views','id','method_url','cache_time','description']
  569. output = o.to_dict(keys=keys)
  570. if o.site and o.site.id > 0:
  571. output['site'] = o.site.to_dict(keys=['name','base_url'])
  572. if o.app and o.app.id > 0:
  573. output['app'] = o.app.to_dict(keys=['name','description','authn','base_url'])
  574. return output
  575. return None
  576. def action_get_list(self,q=None):
  577. if q:
  578. qry = self.db.session.query(Service).filter(and_(
  579. Service.name.like('%' + q + '%'),Service.list_public==True))
  580. else:
  581. qry = self.db.session.query(Service).filter(Service.list_public==True)
  582. log.debug("in services list, qry = %s" % qry)
  583. self.qry = qry
  584. class AppApiHandler(ApiSecureHandler):
  585. object_cls = App
  586. def action_get_object(self,id, data_dict = {}):
  587. if isinstance(id,int) and self.id > 0:
  588. self.object = App.saget(id)
  589. elif isinstance(id,int) and self.id == 0:
  590. if 'slug' in data_dict:
  591. self.object = App.by_slug(self.site.id,slug=data_dict['slug'])
  592. else:
  593. self.object = App.by_slug(site_id=self.site.id,slug=self.id)
  594. if self.object:
  595. self.qry = [self.object]
  596. else:
  597. self.set_status(404)
  598. log.error("no service %s" % self.id)
  599. def json_formatter(self,o):
  600. if o:
  601. keys=['slug','name','owner_id','site_id',
  602. 'list_public','id','base_url','url_format','authn','description']
  603. output = o.to_dict(keys=keys)
  604. output['services'] = []
  605. for svc in o.services:
  606. output['services'].append(svc.to_dict(keys=['key','name','format','url',
  607. 'views','id','method_url','cache_time','description']) )
  608. return output
  609. return None
  610. def action_get_list(self,q=None):
  611. if q:
  612. qry = self.db.session.query(Service).filter(and_(
  613. App.name.like('%' + q + '%'),App.list_public==True))
  614. else:
  615. qry = self.db.session.query(App).filter(App.list_public==True)
  616. log.debug("in services list, qry = %s" % qry)
  617. self.qry = qry
  618. class SiteApiHandler(ApiSecureHandler):
  619. object_cls = Site
  620. def object_load_dict(self,o,data_dict):
  621. 'http post handle args'
  622. if 'settings' in data_dict:
  623. attr_list = []
  624. if isinstance(data_dict['settings'],list):
  625. attr_list = data_dict['settings']
  626. elif isinstance(data_dict['settings'],dict):
  627. attr_list = [data_dict['settings']]
  628. for attr in attr_list:
  629. object_type = "site" if not 'object_type' in attr else attr['object_type']
  630. category = "config" if not 'category' in attr else attr['category']
  631. encoding = 'str' if not 'encoding' in attr else attr['encoding']
  632. event_type = None if not 'event_type' in attr else attr['event_type']
  633. requires = None if not 'requires' in attr else attr['requires']
  634. if requires and isinstance(requires,(dict,list)):
  635. requires = json.dumps(requires)
  636. log.debug('requires=%s' % requires)
  637. o.add_attribute(attr['name'],attr['value'],event_type=event_type,
  638. encoding=encoding,category=category,requires=requires)
  639. data_dict.pop('settings')
  640. if 'delete_settings' in data_dict:
  641. for attr in data_dict['delete_settings']:
  642. a = o.get_attribute(attr['name'])
  643. log.debug('deletting settings = id=%s, name=%s' % (ua.id, ua.name))
  644. a.delete()
  645. data_dict.pop('delete_settings')
  646. return data_dict
  647. def _add_object(self,data_dict):
  648. if self.site.is_sysadmin == True or self.site.id == self.id:
  649. super(SiteApiHandler, self)._add_object(data_dict=data_dict)
  650. else:
  651. self.set_status(403)
  652. def action_get_object(self,id, data_dict = {}):
  653. if self.site.is_sysadmin == True or (self.site.id == self.id or self.site.slug == self.id):
  654. if type(id) == int and id > 0:
  655. self.object = Site.saget(id)
  656. elif type(id) == int and id == 0 and 'slug' in data_dict:
  657. log.debug("they asked for id = 0, lets ignore and doublecheck slug = %s" % data_dict['slug'])
  658. self.object = Site.by_slug(data_dict['slug'])
  659. else:
  660. log.debug("they asked for id = %s, lets ignore and doublecheck slug" % (self.id))
  661. self.object = Site.by_slug(self.id)
  662. if self.object:
  663. self.qry = [self.object]
  664. else:
  665. self.set_status(404)
  666. log.error("no site %s, status=%s" % (self.id, self._status_code))
  667. def json_formatter(self,o):
  668. if o:
  669. keys=['email','name','slug','description','id','extra_json',
  670. 'base_url','site_url','created','enabled','public']
  671. output = o.to_dict(keys=keys)
  672. if o.apps:
  673. output['apps'] = []
  674. for app in o.apps:
  675. appd = {}
  676. appd = app.to_dict(keys=['name','description','authn','base_url'])
  677. appd['services'] = []
  678. for svc in app.services:
  679. appd['services'].append(svc.to_dict(keys=['key','name','format','url',
  680. 'views','id','method_url','cache_time','description']) )
  681. output['apps'].append(appd)
  682. if o.settings:
  683. output['settings'] = []
  684. for attr in o.settings:
  685. output['settings'].append(attr.to_dict(keys=['id','name',
  686. 'value','category','event_type','object_type','encoding']) )
  687. return output
  688. return None
  689. def action_get_list(self,q=None):
  690. if self.site.is_sysadmin:
  691. if q:
  692. qry = self.db.session.query(Site).filter(and_(
  693. Service.name.like('%' + q + '%'),Service.list_public==True))
  694. else:
  695. qry = self.db.session.query(Service).filter(Service.list_public==True)
  696. log.debug("in services list, qry = %s" % qry)
  697. self.qry = qry
  698. class ObjectApiHandler(ApiSecureHandler):
  699. object_cls = Object
  700. def delete_by_get(self):
  701. self.action_get_object(self.id)
  702. if self.object and hasattr(self.object,'delete'):
  703. log.debug("cutom delete by get %s" % self.id)
  704. self.object.delete()
  705. self.object = None
  706. self.qry = None
  707. self.set_status(204)
  708. else:
  709. log.error("not found? %s, %s" % (self.noun, self.id))
  710. def object_load_dict(self,o,data_dict):
  711. 'http post handle args'
  712. if 'post_type' in data_dict:
  713. o.post_type = data_dict['post_type']
  714. data_dict.pop('post_type')
  715. if 'demisauce_id' in data_dict:
  716. o.person_id = int(data_dict['demisauce_id'])
  717. data_dict.pop('demisauce_id')
  718. p = Person.get(self.site.id,o.person_id)
  719. o.displayname = p.displayname
  720. return data_dict
  721. def posts(self,q=None):
  722. if not q:
  723. log.debug("In posts.q limit = %s" % self.limit)
  724. qry = self.db.session.query(Object).filter(Object.post_type=='post').options(eagerload('person')).limit(self.limit)
  725. if self.start > 0:
  726. log.debug("self.start = %s" % self.start)
  727. qry = qry.offset(self.start)
  728. else:
  729. qry = self.db.session.query(self.__class__.object_cls).filter(
  730. self.__class__.object_cls.name.like('%' + q + '%'))
  731. self.qry = qry
  732. def action_get_list(self,q=None):
  733. if not q:
  734. log.debug("In list.q limit = %s" % self.limit)
  735. qry = self.db.session.query(Object).options(eagerload('person')).limit(self.limit)
  736. if self.start > 0:
  737. log.debug("self.start = %s" % self.start)
  738. qry = qry.offset(self.start)
  739. else:
  740. qry = self.db.session.query(self.__class__.object_cls).filter(
  741. self.__class__.object_cls.name.like('%' + q + '%'))
  742. self.qry = qry
  743. def json_formatter(self,o):
  744. if o:
  745. output = o.to_dict()
  746. return output
  747. return None
  748. def action_get_object(self,id, data_dict = {}):
  749. #if 'foreign_id' in data_dict and data_dict['foreign_id']:
  750. # self.object = Object.saget(data_dict['foreign_id'])
  751. #if not self.object:
  752. if isinstance(id,int) and id> 0:
  753. self.object = Object.saget(id)
  754. elif isinstance(id,int) and id == 0:
  755. pass
  756. else:
  757. log.debug("calling by_slug = %s" % (id))
  758. self.object = Object.by_slug(site_id=self.site.id,slug=id)
  759. #log.debug('METHOD: %s' % self.request)
  760. if self.object:
  761. self.qry = [self.object]
  762. elif self.request.method in ('POST','PUT','DELETE'):
  763. self.qry = []
  764. elif self.request.method == 'GET':
  765. self.set_status(404)
  766. log.error("no object %s" % self.id)
  767. class GroupApiHandler(ApiSecureHandler):
  768. object_cls = Group
  769. def after_save(self,data_dict):
  770. 'http post handle args'
  771. if 'emails' in data_dict:
  772. if isinstance(data_dict['emails'],(str,unicode)):
  773. log.debug("trying to load = %s" % (data_dict['emails']))
  774. data_dict['emails'] = json.loads(data_dict['emails'])
  775. assert type(data_dict['emails']) is list
  776. self.object.add_memberlist(data_dict['emails'])
  777. self.object.save()
  778. def add_users_POST(self):
  779. pass
  780. def action_get_object(self,id, data_dict = {}):
  781. if type(id) == int:
  782. self.object = Group.get(site_id=self.site.id,id=id)
  783. else:
  784. self.object = Group.by_slug(site_id=self.site.id,slug=id)
  785. if self.object:
  786. self.qry = [self.object]
  787. else:
  788. log.error("no Group %s" % self.id)
  789. def send(self):
  790. log.error("in send of email api")
  791. emailjson = json.loads(self.request.body)
  792. if emailjson and 'template_name' in emailjson:
  793. log.error("weehah, body json = %s" % emailjson)
  794. #TODO: revamp and use self.db.gearman_client
  795. gearman_client = GearmanClient(options.gearman_servers)
  796. gearman_client.do_task(Task("email_send",self.request.body, background=True))
  797. def json_formatter(self,o):
  798. if o:
  799. output = o.to_dict(keys=['name','slug','id','url','extra_json'])
  800. if o.members:
  801. members = []
  802. for m in o.members:
  803. members.append(m.member.to_dict(keys=['displayname','email','id']))
  804. output['members'] = members
  805. return output
  806. return None
  807. def action_get_list(self,q=None):
  808. if q:
  809. qry = self.db.session.query(Group).filter(and_(
  810. Group.name.like('%' + q + '%'),Template.site_id==self.site.id))
  811. else:
  812. qry = self.db.session.query(Group).filter(Template.site_id==self.site.id)
  813. log.debug("in group list, qry = %s" % qry)
  814. self.qry = qry
  815. class HookApiHandler(BaseHandler):
  816. def _do_proxy(self,*args,**kwargs):
  817. log.debug("PROXY %s *args, **kwargs = %s, %s" % (kwargs['method'],args,kwargs))
  818. log.debug("PROXY %s %s, body=%s" % (kwargs['method'],self.request.arguments,self.request.body))
  819. http = tornado.httpclient.HTTPClient()
  820. response = http.fetch("http://192.168.1.43/blog/xmlrpc.php",
  821. method=kwargs['method'],
  822. headers=self.request.headers,
  823. body=self.request.body)
  824. log.debug(str(response))
  825. self.write(response.body)
  826. def get(self,*args):
  827. log.debug("HOOK?PROXY GET *args, **kwargs = %s" % (str(args)))
  828. if args and len(args) > 0 and 'proxy' == args[0]:
  829. self._do_proxy(*args,method='GET')
  830. else:
  831. pass
  832. def post(self,*args):
  833. if args and len(args) > 0 and 'proxy' == args[0]:
  834. self._do_proxy(*args,method="POST")
  835. else:
  836. log.debug("POST %s" % self.request.arguments)
  837. class PubSubApiHandler(ApiBaseHandler):
  838. object_cls = Object
  839. def get(self,noun=None,requestid='all',action=None,format="json"):
  840. self.normalize_args(noun, requestid,action,format)
  841. # http://www.smugmug.com/hack/feed.mg?Type=keyword&Data=locifood&format=atom10
  842. # http://api.flickr.com/services/feeds/photos_public.gne?tags=locifood&lang=en-us&format=rss_200
  843. log.debug("API: noun=%s, id=%s, action=%s, format=%s, url=%s, start=%s, limit=%s" % (self.noun,self.id,self.action,self.format,self.request.path,self.start,self.limit))
  844. #http://dev.demisauce.com/api/pubsub/?apikey=c97933249d23a72362ec0f3da09b2c60
  845. action = self.get_argument("hub.mode",None)
  846. if action in ('subscribe','unsubscribe'):
  847. self.write(self.get_argument('hub.challenge'))
  848. topic = self.get_argument("hub.topic")
  849. log.debug("subscribing to feed %s" % (topic))
  850. else:
  851. log.debug("should parse")
  852. #hub.topic=http%3A%2F%2Fsuperfeedr.com%2Fdummy.xml
  853. #hub.verify_token=
  854. #hub.challenge=c5ee7a111126fdddc42e89ddf3fb3aad
  855. #hub.mode=subscribe
  856. pass
  857. log.debug("in get status = %s" % (self._status_code))
  858. #self.render_to_format()
  859. def action_get_list(self,q=None):
  860. pass
  861. self.qry = []
  862. def posts(self,q=None):
  863. if not q:
  864. log.debug("In posts.q limit = %s" % self.limit)
  865. qry = self.db.session.query(Object).filter(Object.post_type=='post').options(eagerload('person')).limit(self.limit)
  866. if self.start > 0:
  867. log.debug("self.start = %s" % self.start)
  868. qry = qry.offset(self.start)
  869. else:
  870. qry = self.db.session.query(self.__class__.object_cls).filter(
  871. self.__class__.object_cls.name.like('%' + q + '%'))
  872. self.qry = qry
  873. def action_get_object(self,id, data_dict = {}):
  874. if isinstance(id,int) and self.id > 0:
  875. self.object = Object.saget(id)
  876. elif isinstance(id,int) and self.id == 0:
  877. pass
  878. else:
  879. log.debug("calling by_slug = %s" % (id))
  880. self.object = Object.by_slug(site_id=self.site.id,slug=id)
  881. log.debug('METHOD: %s' % dir(self.request))
  882. if self.object:
  883. self.qry = [self.object]
  884. elif self.request.method in ('POST','PUT','DELETE'):
  885. self.qry = []
  886. elif self.request.method == 'GET':
  887. self.set_status(404)
  888. log.error("no object %s" % self.id)
  889. """ GET : get
  890. POST : add/update
  891. DELETE: delete
  892. /api/noun/id/(action|filter|property)
  893. (r"/api/(.*?)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", ApiSecureHandler),
  894. (r"/api/(.*?)/([0-9]*?|.*?|all|list)(.json|.xml|.custom)?", ApiSimpleHandler),
  895. """
  896. _controllers = [
  897. (r"/api/(pubsub)/(.*?)",PubSubApiHandler),
  898. (r"/api/(hook|webhook)(?:\/)?(.*?)",HookApiHandler),
  899. (r"/api/(tbdproxy|proxy)/(.*?)",HookApiHandler),
  900. (r"/api/(activity)/(.*?)", ActivityApiHandler),
  901. (r"/api/(user|person)/(.*?)/(init_user|tbdmorestuff)", PersonAnonApi),
  902. (r"/api/(user|person)/([0-9]*?|.*?)/(.*?).(json|xml|custom)?", PersonApiHandler),
  903. (r"/api/(user|person)/(.*?).(?:json|xml|custom)", PersonApiHandler),
  904. (r"/api/(user|person)/(.*?)", PersonApiHandler),
  905. (r"/api/(group)/([0-9]*?|.*?)/(.*?).(json|xml|custom)?", GroupApiHandler),
  906. (r"/api/(group)/(.*?).(?:json|xml|custom)", GroupApiHandler),
  907. (r"/api/(group)/(.*?)", GroupApiHandler),
  908. (r"/api/(site)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", SiteApiHandler),
  909. (r"/api/(site)/(.*?)(.json|.xml|.custom)", SiteApiHandler),
  910. (r"/api/(app)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", AppApiHandler),
  911. (r"/api/(app)/(.*?)(.json|.xml|.custom)", AppApiHandler),
  912. (r"/api/(email|template)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", TemplateApiHandler),
  913. (r"/api/(email|template)/(.*?)(.json|.xml|.custom)", TemplateApiHandler),
  914. (r"/api/(service)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", ServiceApiHandler),
  915. (r"/api/(service)/(.*?)(?:.json|.xml|.custom)", ServiceApiHandler),
  916. (r"/api/(object|content)/([0-9]*?|.*?|all|list)/(.*?)(?:\.)?(json|xml|custom)?", ObjectApiHandler),
  917. (r"/api/(object|content)/(.*?)(?:.json|.xml|.custom)", ObjectApiHandler),
  918. ]