Latest Version: 0.9.6.2
  Dashboard > Pylons Cookbook > ... > Recipes > A Pylons controller with MoinMoin as a WSGI callable
  Pylons Cookbook Log In | Sign Up   View a printable version of the current page.  
  A Pylons controller with MoinMoin as a WSGI callable
Added by Graham Higgins, last edited by Max Ischenko on Jul 30, 2007  (view change) show comment
Labels: 

Name Space Section Page Version Status Curator Reviewed Author(s)
Title Pylons CookBook Recipes A Pylons controller with MoinMoin as a WSGI callable 1.0 Draft Graham Higgins False Graham Higgins
Includes references to PylonsHQ-located documentation.

A Pylons controller with MoinMoin as a WSGI callable

One of the benefits of WSGI middleware apps is that their positioning in the call stack enables them to add data to the environment, to be used by upstream applications. One example of this is authentication / authorization: if you use the AuthKit middleware app, you can set things up so that by the time your controller is called, any authentication / authorization data is readily available. This is useful if you have a need for site-wide authentication and authorization.

In this simple recipe we demonstrate the principle with MoinMoin ...

Introduction

This is a simple walk through of how to plug MoinMoin into a Pylons controller, demonstrating that in about 20 minutes you can have a full-featured wiki with access control driven by the AuthKit WSGI authentication and authorization tool set.

In essence it is a lightweight demonstration of the intriguingly powerful capabilities of WSGI (defined in PEP 333).

Requirements are Pylons 0.9.4.1 (or later) with the AuthKit middleware ( easy_install AuthKit, see PylonsWithAuthKitForward PylonsHQ wiki page for how to configure AuthKit for use with Pylons).

The Process

The four-step sequence is:

  1. add a Pylons wiki controller and a route;
  2. modify the standard error mapper to allow MoinMoin to treat 404s as requests to create a new wiki page.
  3. install MoinMoin
  4. create a new wiki instance and configure it

WSGI makes this so easy that the bulk of the task is actually the setting up and configuring of a standard MoinMoin wiki instance.

There are a couple of deviations from the usual MoinMoin setup procedure (which I will detail) but other than that, it's basically a matter of following the standard process of wiki instance creation as described in the MoinMoin documentation.

Step 1 - create a wiki controller and add a route

The first step is to create a controller which will hand off the request to MoinMoin, collect the resulting response and pass it back to paste.

Create a new file: myproject/controllers/wiki.py and add the following as its content (changing the two instances of myproject to your app's name):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from myproject.lib.base import *
import pylons
from pylons import request

from authkit.permissions import NotAuthenticatedError
from authkit.pylons_adaptors import authorize
from authkit.permissions import RemoteUser

# I keep a separate copy of MoinMoin in myproject/parts, specially for
# hacking on and here I override the usual site-packages 
# location. You could just comment out or delete the next two lines.
import sys
sys.path.insert(0,'%(here)s/myproject/parts')

from MoinMoin.server import wsgi
import StringIO, urllib

class WikiController(BaseController):
    def index(self):
        request._current_obj().headers['X-Moin-Location'] = '/wiki/'
        return wsgi.moinmoinApp(request.environ, self.start_response)

    def moinpage(self, *args, **kwargs):
        # Thanks to ianbicking for a workaround of an empty wsgi.input value
        if request.environ.get('paste.parsed_formvars', False):
            wsgi_input = urllib.urlencode(
                request.environ['paste.parsed_formvars'][0], True)
            request.environ['wsgi.input'] = StringIO.StringIO(wsgi_input)
            request.environ['CONTENT_LENGTH'] = len(wsgi_input)
        # Uncomment this if you're using PrefixMiddleware
        # request.environ['SCRIPT_NAME'] = ''
        request._current_obj().headers['X-Moin-Location'] = '/wiki/'
        return wsgi.moinmoinApp(request.environ, self.start_response)

(Ian Bicking observed that the workaround may well become redundant at some point in the future.)

Next, edit myproject/config/routing.py, adding the following route:

1
map.connect('wiki/*url', controller='wiki', action='moinpage')

Step 2 - create a specialised error mapper

Wikis are a special case in which the usual '404 File not found' is not treated as an error but instead is interpreted as a request to create a new page. This is achieved by changing the default Pylons error-handling behavior. The Error Documents page on the PylonsHQ wiki has full details.

Edit myproject/config/middleware.py and add this customized error-mapper just before the definition of make_app:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from urllib import urlencode
from paste.deploy.converters import asbool

def local_error_mapper(code, message, environ, global_conf=None, **kw):
    if global_conf is None:
        global_conf = {}
    codes = [401, 403, 404]
    # Suppress 404 as error for wiki controller to allow new page creation.
    if environ['pylons.routes_dict']['controller'] == 'wiki': 
        codes.remove(404)
    if not asbool(global_conf.get('debug')):
        codes.append(500)
    if code in codes:
        from pylons.util import get_prefix
        url = '%s/error/document/?%s' % (get_prefix(environ),
                                         urlencode({'message':message, 
                                                    'code':code}))
        return url

Now we need to change the ErrorDocuments definition (same file, further down the code) to use the new error mapper:

1
2
3
# Display error documents for 401, 403, 404 status codes (if debug is disabled also
# intercepts 500)
app = ErrorDocuments(app, global_conf, mapper=local_error_mapper, **app_conf)

That's it, you're done with hacking Pylons. Pylons doesn't need anything more in order to be able to run MoinMoin from a controller, all that remains is just normal MoinMoin set-up.

This is what I meant by the "intriguingly powerful capabilities of WSGI".

Step 3 - install MoinMoin

MoinMoin does not yet support easy_install eggs, so you need to visit the download page of the MoinMoin website, download the current version (1.5.7 at the time of writing) and expand it. From the expanded directory, you can either run python setup.py install or easy_install.

Briefly familiarize yourself with the basic setup and configuration of MoinMoin, especially wiki instance creation. For those in a terminal hurry: each wiki instance needs a copy of the *share directory* that was created when MoinMoin was installed.

The share directory is usually PREFIX/share/moin and it is where the templates are located, these are the directories and files in which we are interested:

/usr/share/moin/data                        wiki pages, users, cache, etc.
/usr/share/moin/underlay                    wiki pages
/usr/share/moin/config/wikiconfig.py        example configuration file
/usr/share/moin/htdocs                      html support

Step 4: create and configure the wiki instance

In your project space, at the same level as models, create a new directory called parts and inside that, create another directory called wiki (or, if you already have a system for handling extra libs and stuff, just adapt as appropriate for your scheme).

Copy files and directories from the moin share directory so that your parts and wiki directories looks like this:

myproject/parts/wiki/data/
myproject/parts/wiki/underlay/
myproject/parts/wikiconfig.py

Copy the htdocs directory from the share directory into your app's public directory, changing its name from htdocs to moin, e.g.:

cp -R /usr/share/moin/htdocs myproject/public/moin

The MoinMoin help docs are the authoritative source for configuration details, so I'll just describe the changes to myproject/part/wikiconfig.py which are specific to running the instance as part of a Pylons app.

Here's a diff that shows the basic changes you need to make to the standard wikiconfig.py template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
diff -uNr wikiconfig.py wikiconfigedited.py
--- wikiconfig.py	2006-06-25 14:27:26 +0100
+++ wikiconfigedited.py	2007-02-22 12:12:24 +0000
@@ -30,6 +30,23 @@

 class Config(DefaultConfig):

+    def appauth(request, **kw):
+        user = None
+        try_next = True # if True, moin tries the next auth method
+        username = request.get('auth_username', '')
+		 if username:
+			 from MoinMoin.user import User
+			 # giving auth_username to User constructor means that
+			 # authentication has already been done.
+			 user = User(request, name=username, auth_username=username)
+			 changed = False
+			 if user:
+				 user.create_or_update(changed)
+			 if user and user.valid: # did we succeed making a valid user?
+				 try_next = False # stop processing auth method list
+        return user, try_next
+    auth = [appauth]
+
     # Wiki identity ----------------------------------------------------

     # Site name, used by default for wiki name-logo [Unicode]
@@ -38,7 +55,7 @@
     # Wiki logo. You can use an image, text or both. [Unicode]
     # For no logo or text, use '' - the default is to show the sitename.
     # See also url_prefix setting below!
-    logo_string = u'<img src="/wiki/common/moinmoin.png" alt="MoinMoin Logo">'
+    logo_string = u'<img src="/moin/common/moinmoin.png" alt="MoinMoin Logo">'

     # name of entry page / front page [Unicode], choose one of those:

@@ -65,21 +82,22 @@

     # Where your mutable wiki pages are. You want to make regular
     # backups of this directory.
-    data_dir = './data/'
+    data_dir = 'myproject/parts/wiki/data/'

     # Where read-only system and help page are. You might want to share
     # this directory between several wikis. When you update MoinMoin,
     # you can safely replace the underlay directory with a new one. This
     # directory is part of MoinMoin distribution, you don't have to
     # backup it.
-    data_underlay_dir = './underlay/'
+    data_underlay_dir = 'myproject/parts/wiki/underlay/'

     # Location of your STATIC files (css/png/js/...) - you must NOT use the
     # same for invoking moin.cgi (or, in general, the moin code).
     # url_prefix must be '/wiki' for Twisted and standalone servers.
     # For CGI, it should match your Apache Alias setting.
-    url_prefix = '/wiki'
+    url_prefix = '/moin'

+    url_mappings = {'wiki/':''}

     # Security ----------------------------------------------------------

In summary: add an appauth method to the Config class, this will allow MoinMoin to pick up any authorization data added by the AuthKit middleware.

Note: MoinMoin has its own independent file-based mechanism for storing and maintaining user details and preferences, a close integration of AuthKit and MoinMoin user data will require additional work.

Change the values of logo_string, data_dir, data_underlay_dir & url_prefix to suit and finally add the url_mappings variable and assignment as shown above.

That completes the Pylons-specific additions to the MoinMoin config. You should take the opportunity to set up the remaining half-dozen standard configuration values (e.g. sitename, acl_rights, etc.), following the advice in the MoinMoin docs but if you'd rather see it working and then tweak the config, what we have so far will be sufficient to get MoinMoin up and running.

Check the results

Browse to [http://localhost:5000/wiki/] and start using your 20-minute wiki.

I hope this has demonstrated some of the capabilities of WSGI and has been enough to get you started.

FWIW, I am now running the above setup on a pre-release version of MoinMoin 1.6 (I really like the new "pop-down" comments facility). A small amendment to the MoinMoin code was required in order to bypass a strangeness with the construction of links for the creation of Missing pages in MoinMoin. I'll post an update when I'm satisfied that it's not just a temporary condition related to developments in MoinMoin.

Hi graham
i tried your setup
i works well
except the appauth func, no 'get' available in moin request
i think i will try to write my own auth handlers on moin side
and attachments / files upload dont work at all
no error message nothing, it just redirects to the previous page
any idea about this one? does it work for you?
thanx
alain

Posted by alain at Jun 20, 2007 15:00 | Permalink
Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators
Top