Home | History | Annotate | Download | only in wiki-example-code
      1 import os
      2 import re
      3 from webob import Request, Response
      4 from webob import exc
      5 from tempita import HTMLTemplate
      6 
      7 VIEW_TEMPLATE = HTMLTemplate("""\
      8 <html>
      9  <head>
     10   <title>{{page.title}}</title>
     11  </head>
     12  <body>
     13 <h1>{{page.title}}</h1>
     14 {{if message}}
     15 <div style="background-color: #99f">{{message}}</div>
     16 {{endif}}
     17 
     18 <div>{{page.content|html}}</div>
     19 
     20 <hr>
     21 <a href="{{req.url}}?action=edit">Edit</a>
     22  </body>
     23 </html>
     24 """)
     25 
     26 EDIT_TEMPLATE = HTMLTemplate("""\
     27 <html>
     28  <head>
     29   <title>Edit: {{page.title}}</title>
     30  </head>
     31  <body>
     32 {{if page.exists}}
     33 <h1>Edit: {{page.title}}</h1>
     34 {{else}}
     35 <h1>Create: {{page.title}}</h1>
     36 {{endif}}
     37 
     38 <form action="{{req.path_url}}" method="POST">
     39  <input type="hidden" name="mtime" value="{{page.mtime}}">
     40  Title: <input type="text" name="title" style="width: 70%" value="{{page.title}}"><br>
     41  Content: <input type="submit" value="Save">
     42  <a href="{{req.path_url}}">Cancel</a>
     43    <br>
     44  <textarea name="content" style="width: 100%; height: 75%" rows="40">{{page.content}}</textarea>
     45    <br>
     46  <input type="submit" value="Save">
     47  <a href="{{req.path_url}}">Cancel</a>
     48 </form>
     49 </body></html>
     50 """)
     51 
     52 class WikiApp(object):
     53 
     54     view_template = VIEW_TEMPLATE
     55     edit_template = EDIT_TEMPLATE
     56 
     57     def __init__(self, storage_dir):
     58         self.storage_dir = os.path.abspath(os.path.normpath(storage_dir))
     59 
     60     def __call__(self, environ, start_response):
     61         req = Request(environ)
     62         action = req.params.get('action', 'view')
     63         page = self.get_page(req.path_info)
     64         try:
     65             try:
     66                 meth = getattr(self, 'action_%s_%s' % (action, req.method))
     67             except AttributeError:
     68                 raise exc.HTTPBadRequest('No such action %r' % action)
     69             resp = meth(req, page)
     70         except exc.HTTPException, e:
     71             resp = e
     72         return resp(environ, start_response)
     73 
     74     def get_page(self, path):
     75         path = path.lstrip('/')
     76         if not path:
     77             path = 'index'
     78         path = os.path.join(self.storage_dir, path)
     79         path = os.path.normpath(path)
     80         if path.endswith('/'):
     81             path += 'index'
     82         if not path.startswith(self.storage_dir):
     83             raise exc.HTTPBadRequest("Bad path")
     84         path += '.html'
     85         return Page(path)
     86 
     87     def action_view_GET(self, req, page):
     88         if not page.exists:
     89             return exc.HTTPTemporaryRedirect(
     90                 location=req.url + '?action=edit')
     91         if req.cookies.get('message'):
     92             message = req.cookies['message']
     93         else:
     94             message = None
     95         text = self.view_template.substitute(
     96             page=page, req=req, message=message)
     97         resp = Response(text)
     98         if message:
     99             resp.delete_cookie('message')
    100         else:
    101             resp.last_modified = page.mtime
    102             resp.conditional_response = True
    103         return resp
    104 
    105     def action_view_POST(self, req, page):
    106         submit_mtime = int(req.params.get('mtime') or '0') or None
    107         if page.mtime != submit_mtime:
    108             return exc.HTTPPreconditionFailed(
    109                 "The page has been updated since you started editing it")
    110         page.set(
    111             title=req.params['title'],
    112             content=req.params['content'])
    113         resp = exc.HTTPSeeOther(
    114             location=req.path_url)
    115         resp.set_cookie('message', 'Page updated')
    116         return resp
    117 
    118     def action_edit_GET(self, req, page):
    119         text = self.edit_template.substitute(
    120             page=page, req=req)
    121         return Response(text)
    122 
    123 class Page(object):
    124     def __init__(self, filename):
    125         self.filename = filename
    126 
    127     @property
    128     def exists(self):
    129         return os.path.exists(self.filename)
    130 
    131     @property
    132     def title(self):
    133         if not self.exists:
    134             # we need to guess the title
    135             basename = os.path.splitext(os.path.basename(self.filename))[0]
    136             basename = re.sub(r'[_-]', ' ', basename)
    137             return basename.capitalize()
    138         content = self.full_content
    139         match = re.search(r'<title>(.*?)</title>', content, re.I|re.S)
    140         return match.group(1)
    141 
    142     @property
    143     def full_content(self):
    144         f = open(self.filename, 'rb')
    145         try:
    146             return f.read()
    147         finally:
    148             f.close()
    149 
    150     @property
    151     def content(self):
    152         if not self.exists:
    153             return ''
    154         content = self.full_content
    155         match = re.search(r'<body[^>]*>(.*?)</body>', content, re.I|re.S)
    156         return match.group(1)
    157 
    158     @property
    159     def mtime(self):
    160         if not self.exists:
    161             return None
    162         else:
    163             return int(os.stat(self.filename).st_mtime)
    164 
    165     def set(self, title, content):
    166         dir = os.path.dirname(self.filename)
    167         if not os.path.exists(dir):
    168             os.makedirs(dir)
    169         new_content = """<html><head><title>%s</title></head><body>%s</body></html>""" % (
    170             title, content)
    171         f = open(self.filename, 'wb')
    172         f.write(new_content)
    173         f.close()
    174 
    175 if __name__ == '__main__':
    176     import optparse
    177     parser = optparse.OptionParser(
    178         usage='%prog --port=PORT'
    179         )
    180     parser.add_option(
    181         '-p', '--port',
    182         default='8080',
    183         dest='port',
    184         type='int',
    185         help='Port to serve on (default 8080)')
    186     parser.add_option(
    187         '--wiki-data',
    188         default='./wiki',
    189         dest='wiki_data',
    190         help='Place to put wiki data into (default ./wiki/)')
    191     options, args = parser.parse_args()
    192     print 'Writing wiki pages to %s' % options.wiki_data
    193     app = WikiApp(options.wiki_data)
    194     from wsgiref.simple_server import make_server
    195     httpd = make_server('localhost', options.port, app)
    196     print 'Serving on http://localhost:%s' % options.port
    197     try:
    198         httpd.serve_forever()
    199     except KeyboardInterrupt:
    200         print '^C'
    201