/kai/controllers/docs.py

https://bitbucket.org/bbangert/kai/ · Python · 178 lines · 134 code · 26 blank · 18 comment · 41 complexity · 0fd019191b7a99f13b27181444e0773f MD5 · raw file

  1. import mimetypes
  2. import logging
  3. import os
  4. import pylons
  5. from couchdb import ResourceConflict
  6. from paste.urlparser import StaticURLParser
  7. from pylons import config, request, response, session, tmpl_context as c
  8. from pylons.controllers.util import abort, redirect
  9. from pylons.decorators import jsonify
  10. from kai.lib.base import BaseController, json, render
  11. from kai.model import Documentation
  12. log = logging.getLogger(__name__)
  13. class DocsController(BaseController):
  14. """Documentation Controller
  15. The Documentation controller handles uploads of docs, as well as
  16. doc display.
  17. """
  18. def __before__(self):
  19. c.active_tab = 'Documentation'
  20. c.active_sub = 'Reference'
  21. def view(self, version, url, language):
  22. # Change the url back to a string (just in case)
  23. url = str(url)
  24. if 'doc_version' not in session or version != session['doc_version']:
  25. session['doc_version'] = version
  26. session.save()
  27. # Check for serving images
  28. if url.startswith('_images/'):
  29. img_srv = StaticURLParser(config['image_dir'])
  30. request.environ['PATH_INFO'] = '/Pylons/%s/%s' % (version, url[8:])
  31. return img_srv(request.environ, self.start_response)
  32. # If the docs are pre-0.9.7, use the old viewer
  33. if version < '0.9.7':
  34. return self._view_old(version, url)
  35. # Check a few other space cases and alter the URL as needed
  36. if url.startswith('glossary/'):
  37. c.active_sub = 'Glossary'
  38. if url.startswith('modules/'):
  39. c.active_sub = 'Modules'
  40. if url == 'modules/':
  41. url = 'modindex'
  42. if url == 'index/':
  43. url = 'genindex'
  44. c.active_sub = 'Index'
  45. if url.endswith('/'):
  46. url = url[:-1]
  47. # Grab the doc from CouchDB
  48. c.doc = Documentation.fetch_doc('Pylons', version, language, url)
  49. if not c.doc:
  50. # Try again with index just in case
  51. url += '/index'
  52. c.doc = Documentation.fetch_doc('Pylons', version, language, url)
  53. if not c.doc:
  54. abort(404)
  55. if url == 'modindex':
  56. return render('/docs/modindex.mako')
  57. if url == 'genindex':
  58. return render('/docs/genindex.mako')
  59. if url == 'objects.inv':
  60. response.content_type = 'text/plain'
  61. return c.doc['content']
  62. if url == 'search':
  63. redirect(pylons.url('search'))
  64. return render('/docs/view.mako')
  65. def _view_old(self, version, url):
  66. if request.path_info.endswith('docs') or request.path_info.endswith('docs/'):
  67. redirect(pylons.url('/docs/%s/' % str(version)))
  68. base_path = os.path.normpath(config.get('doc_dir'))
  69. if url == 'index':
  70. url = 'index.html'
  71. c.url = os.path.normpath(os.path.join(base_path, version, url))
  72. # Prevent circumvention of the base docs path and bad paths
  73. if not c.url.startswith(base_path) or not os.path.exists(c.url):
  74. if version > '0.9.5':
  75. redirect(pylons.url('cdocs', page='Home'))
  76. abort(404)
  77. c.version = version
  78. return render('/docs/load_content.mako')
  79. @jsonify
  80. def upload_image(self):
  81. dockey = config['doc.security_key']
  82. if request.environ['HTTP_AUTHKEY'] != dockey:
  83. abort(401)
  84. doc = request.body
  85. version = request.GET.get('version')
  86. project = request.GET.get('project')
  87. name = request.GET.get('name')
  88. if not version or not project or not name:
  89. response.status = 500
  90. return dict(status='error', reason='No version/project combo found')
  91. fdir = os.path.join(config['image_dir'], project, version)
  92. # Ensure this didn't escape our current dir, we must be under images
  93. if not fdir.startswith(config['image_dir']):
  94. abort(500)
  95. fname = os.path.join(fdir, name)
  96. # Ensure we're under the parent dir we should be
  97. if not fname.startswith(fdir):
  98. abort(500)
  99. ensure_dir(fdir)
  100. try:
  101. with open(os.path.join(fdir, name), 'w') as w:
  102. w.write(doc)
  103. except:
  104. abort(500)
  105. else:
  106. return dict(status='ok')
  107. @jsonify
  108. def upload(self):
  109. dockey = config['doc.security_key']
  110. if request.environ['HTTP_AUTHKEY'] != dockey:
  111. abort(401)
  112. try:
  113. doc = json.loads(request.body)
  114. except:
  115. response.status = 500
  116. return dict(status='error', reason='Unable to parse JSON')
  117. doc['type'] = 'Documentation'
  118. update = False
  119. # Check to see we don't have it already
  120. existing_doc = list(Documentation.doc_key(self.db)[doc['filename'], doc['version'], doc['language'], doc['project']])
  121. if existing_doc:
  122. existing_doc = dict(existing_doc[0].doc)
  123. update = True
  124. try:
  125. if update:
  126. existing_doc.update(doc)
  127. self.db.update([existing_doc])
  128. else:
  129. self.db.create(doc)
  130. except ResourceConflict:
  131. response.status = 409
  132. return dict(status='conflict')
  133. return dict(status='ok')
  134. @jsonify
  135. def delete_revision(self, project, version):
  136. dockey = config['doc.security_key']
  137. if request.environ['HTTP_AUTHKEY'] != dockey:
  138. abort(401)
  139. # Documentation.delete_revision(project, version)
  140. return dict(status='ok')
  141. def ensure_dir(dir):
  142. """Copied from Mako, ensures a directory exists in the filesystem"""
  143. tries = 0
  144. while not os.path.exists(dir):
  145. try:
  146. tries += 1
  147. os.makedirs(dir, 0750)
  148. except:
  149. if tries > 5:
  150. raise