[Twisted-Python] StikiWiki and textarea/woven/model problem.

Neil Blakey-Milner nbm at mithrandr.moria.org
Thu Mar 13 09:11:46 EST 2003


Hi,

Firstly, StikiWiki is a simple example of a somewhat Wiki-like web
application in Twisted that can create and edit reStructuredText
documents, and display them as HTML.  It's attached as stikiwiki.py.

Well, you can create documents.  Editing isn't as much editing as
rewriting.  I'm using a textarea for input, and since I haven't looked
all that much at woven, I can't figure out why its contents aren't
replaced with the existing content (via getData on the given model).

So, at the moment, you can create a page (localhost:9080/asdf/create),
type in some reST, and then go to localhost:9080/asdf and view it.  Then
go to localhost:9080/asdf/create and rewrite it.  I want to figure out
why the model's getData doesn't get involved.

Any help greatly appreciated.

Thanks,

Neil
-- 
Neil Blakey-Milner
nbm at mithrandr.moria.org
-------------- next part --------------
#!/usr/local/bin/python
#
# Copyright (c) 2003 Neil Blakey-Milner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

from twisted.cred import service
from twisted.internet import app, reactor
from twisted.web import resource, server, static
from twisted.web.woven import model, page

from docutils import core, io
from StringIO import StringIO
from cog import db, base

entry_xhtml = """
<html>
    <head>
        <title model="title">Quotes Galore!</title>
        <style>.quote {color: green;}</style>
    </head>

    <body>
        <h1 model="title">New Page!</h1>

        <form action="">
            <textarea rows="15" cols="50" name="entry" model="entry"
                controller="Anything" >
                Something
            </textarea>
            <input type="submit" />
        </form>
    </body>
</html>
"""

default_css = """
/*
:Author: David Goodger
:Contact: goodger at users.sourceforge.net
:date: $Date: 2002/07/27 14:55:52 $
:version: $Revision: 1.12 $
:copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
*/

a.footnote-reference {
  font-size: smaller ;
  vertical-align: super }

a.target {
  color: blue }

a.toc-backref {
  text-decoration: none ;
  color: black }

dd {
  margin-bottom: 0.5em }

div.abstract {
  margin: 2em 5em }

div.abstract p.topic-title {
  font-weight: bold ;
  text-align: center }

div.attention, div.caution, div.danger, div.error, div.hint,
div.important, div.note, div.tip, div.warning {
  margin: 2em ;
  border: medium outset ;
  padding: 1em }

div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
  color: red ;
  font-weight: bold ;
  font-family: sans-serif }

div.hint p.admonition-title, div.important p.admonition-title,
div.note p.admonition-title, div.tip p.admonition-title {
  font-weight: bold ;
  font-family: sans-serif }

div.dedication {
  margin: 2em 5em ;
  text-align: center ;
  font-style: italic }

div.dedication p.topic-title {
  font-weight: bold ;
  font-style: normal }

div.figure {
  margin-left: 2em }

div.footer, div.header {
  font-size: smaller }

div.system-messages {
  margin: 5em }

div.system-messages h1 {
  color: red }

div.system-message {
  border: medium outset ;
  padding: 1em }

div.system-message p.system-message-title {
  color: red ;
  font-weight: bold }

div.topic {
  margin: 2em }

h1.title {
  text-align: center }

h2.subtitle {
  text-align: center }

hr {
  width: 75% }

ol.simple, ul.simple {
  margin-bottom: 1em }

ol.arabic {
  list-style: decimal }

ol.loweralpha {
  list-style: lower-alpha }

ol.upperalpha {
  list-style: upper-alpha }

ol.lowerroman {
  list-style: lower-roman }

ol.upperroman {
  list-style: upper-roman }

p.caption {
  font-style: italic }

p.credits {
  font-style: italic ;
  font-size: smaller }

p.first {
  margin-top: 0 }

p.label {
  white-space: nowrap }

p.topic-title {
  font-weight: bold }

pre.literal-block, pre.doctest-block {
  margin-left: 2em ;
  margin-right: 2em ;
  background-color: #eeeeee }

span.classifier {
  font-family: sans-serif ;
  font-style: oblique }

span.classifier-delimiter {
  font-family: sans-serif ;
  font-weight: bold }

span.field-argument {
  font-style: italic }

span.interpreted {
  font-family: sans-serif }

span.option-argument {
  font-style: italic }

span.problematic {
  color: red }

table {
  margin-top: 0.5em ;
  margin-bottom: 0.5em }

table.citation {
  border-left: solid thin gray ;
  padding-left: 0.5ex }

table.docinfo {
  margin: 2em 4em }

table.footnote {
  border-left: solid thin black ;
  padding-left: 0.5ex }

td, th {
  padding-left: 0.5em ;
  padding-right: 0.5em ;
  vertical-align: baseline }

td.docinfo-name {
  font-weight: bold ;
  text-align: right }

td.field-name {
  font-weight: bold }
"""

class MStikiEntry(model.Model):
    def __init__(self, page, path, value):
        model.Model.__init__(self)
        self.page = page
        self.path = path
        self.value = value

    def getData(self):
        return self.value
        pass

    def setData(self, data):
        p = StikiPage(self.page.parent, self.path, data)
        self.page.parent.putChild(self.path, p)
        print "putChild with %s and %s on %s" % (self.path, p.id, self.page.parent.id)

class StikiPage(object, resource.Resource):
    __persistent__ = 1

    value = """
Uninitialised StikiPage
-----------------------

This StikiPage exists, but doesn't have any content in it."""

    def __init__(self, parent, path, value = None):
        self.errorpage = 0
        self.final = 0
        resource.Resource.__init__(self)
        self.parent = parent
        if value:
            self.value = value
        self.id = path
        base.setDirty(self, 1)

    def getChild(self, path, request):

        if path == "":
            return self

        fullpath = "/".join(request.acqpath[:-2])
        shortpath = request.acqpath[-2]

        self.model = {'entry': MStikiEntry(self, shortpath, self.value), 'title': "New StikiWiki Entry!"}

        if path == "create":
            p = page.Page(self.model, templateFile="create.xhtml")
            p.template = entry_xhtml
            return p

        if path == "delete":
            return StikiErrorPage(self, path, request.uri)

        return StikiErrorPage(self, path, request.uri)

    def render(self, request):
        pub = core.Publisher()
        pub.set_reader('restructuredtext', None, 'restructuredtext')
        pub.set_writer('html')
        pub.set_options()
        pub.options._destination = ''
        pub.options.stylesheet = '/default.css'
        pub.source = io.StringIO(pub.options, source = self.value)
        pub.destination = io.StringIO(pub.options)
        pub.options.report_level = 5
        pub.options.halt_level = 6
        warnings = StringIO()
        pub.options.warning_stream = warnings
        return pub.publish()

    def putChild(self, path, res):
        resource.Resource.putChild(self, path, res)
        base.setDirty(self, 1)

class StikiErrorPage(StikiPage):
    def __init__(self, parent, path, uri, final = 0):
        StikiPage.__init__(self, parent, path)
        self.errorpage = 1
        self.final = final
        self.value = """
No such entry
-------------

There is no such entry in this StikiWiki."""

        if not final:
            if not uri.endswith("/"):
                uri += "/"
            uri += "create"
            self.value += """  `Create it?`_
            
.. _`Create it?`: %s""" % (uri)

    def getChild(self, path, request):
        if self.final:
            return StikiErrorPage(self, path, request.uri, 1)

        if not self.final and path == "":
            self.final = 1
            return self

        fullpath = "/".join(request.acqpath[:-2])
        shortpath = request.acqpath[-2]

        self.model = {'entry': MStikiEntry(self, shortpath, self.value), 'title': "New StikiWiki Entry!"}

        if path == "create":
            p = page.Page(self.model, templateFile="create.xhtml")
            p.template = entry_xhtml
            return p

        #if path == "delete":
        #    return StikiErrorPage(self, path, request.uri)

        #if self.errorpage:
        #    self.final = 1

        return StikiErrorPage(self, path, request.uri, 1)


class StikiSite(object, resource.Resource):
    __persistent__ = 1

    def __init__(self):
        resource.Resource.__init__(self)
        self.id = "site"

    def getChild(self, path, request):
        if path == "":
            return self.getChildWithDefault("Index", request)
        return StikiErrorPage(self, path, request.uri)

    def putChild(self, path, res):
        resource.Resource.putChild(self, path, res)
        base.setDirty(self, 1)

class StikiService(app.ApplicationService):
    def startService(self):
        app.ApplicationService.startService(self)
        reactor.callLater(0, self.addDefaults)

    def addDefaults(self):
        db = self.serviceParent.getServiceNamed("cog").db

        if not db.registry.has_key("stikisite"):
            self.ss = StikiSite()
            db.registry.set("stikisite", self.ss)
        else:
            self.ss = db.registry.get("stikisite")
            self.ss._load()

        self.serviceParent.listenTCP(9080, server.Site(self.ss))

        if "Index" not in self.ss.listStaticNames():
            self.ss.putChild("Index", StikiPage(self.ss, "Index",
                """
Hello World!
-----------

Hey there!"""))

        if "default.css" not in self.ss.listStaticNames():
            self.ss.putChild("default.css", static.Data(default_css, "text/css"))

        db.flush()
        del db

class CogService(app.ApplicationService):
    def __init__(self, path, serviceName, serviceParent=None):
        app.ApplicationService.__init__(self, serviceName, serviceParent=serviceParent)
        self.path = path

    def startService(self):
        self.db = db.openDatabase(self.path)
        self.scheduledWork = reactor.callLater(60, self.doPeriodicWork)

    def stopService(self):
        self.scheduledWork.cancel()
        del self.scheduledWork
        self.db.close()
        del self.db

    def doPeriodicWork(self):
        delay = self.db.do_periodic_work()
        self.scheduledWork = reactor.callLater(delay, self.doPeriodicWork)

    def commit(self):
        self.db.flush()

def main():
    a = app.Application("stikiwiki")
    s = StikiService("stiki", a)
    c = CogService("/tmp/cog/", "cog", a)
    a.run(save=0)

if __name__ == "__main__":
    main()


More information about the Twisted-Python mailing list